From 91df6cab77337eff6fc68398443b8491af38db92 Mon Sep 17 00:00:00 2001 From: Alexandre Duret-Lutz Date: Sat, 23 Feb 2008 00:17:42 +0100 Subject: [PATCH] fix status of lbtt's subtree. Apparently it was messed up during the cvsimport --- lbtt/AUTHORS | 2 +- lbtt/ChangeLog | 26 +- lbtt/NEWS | 180 +- lbtt/README | 22 +- lbtt/configure.ac | 52 +- lbtt/doc/intersectioncheck.txt | 1 + lbtt/doc/lbtt.texi | 1268 +++-- lbtt/doc/testprocedure.txt | 1 + lbtt/doc/texinfo.tex | 7482 ++++++++++++++++++++++++++++++ lbtt/src/BitArray.cc | 10 +- lbtt/src/BitArray.h | 18 +- lbtt/src/Bitset.h | 4 +- lbtt/src/BuchiAutomaton.cc | 621 +-- lbtt/src/BuchiAutomaton.h | 200 +- lbtt/src/BuchiProduct.cc | 10 +- lbtt/src/BuchiProduct.h | 16 +- lbtt/src/Config-lex.ll | 294 +- lbtt/src/Config-parse.yy | 1304 ++---- lbtt/src/Configuration.cc | 1652 ++++--- lbtt/src/Configuration.h | 301 +- lbtt/src/DispUtil.cc | 12 +- lbtt/src/DispUtil.h | 28 +- lbtt/src/EdgeContainer.h | 4 +- lbtt/src/Exception.h | 56 +- lbtt/src/ExternalTranslator.cc | 143 +- lbtt/src/FormulaRandomizer.cc | 10 +- lbtt/src/FormulaRandomizer.h | 56 +- lbtt/src/FormulaWriter.h | 8 +- lbtt/src/Graph.h.in | 321 +- lbtt/src/IntervalList.cc | 19 +- lbtt/src/IntervalList.h | 23 +- lbtt/src/LbtWrapper.h | 4 +- lbtt/src/LbttAlloc.h | 10 +- lbtt/src/Ltl-parse.yy | 14 +- lbtt/src/LtlFormula.cc | 223 +- lbtt/src/LtlFormula.h | 85 +- lbtt/src/NeverClaim-lex.ll | 5 +- lbtt/src/NeverClaim-parse.yy | 4 +- lbtt/src/NeverClaimAutomaton.cc | 20 +- lbtt/src/NeverClaimAutomaton.h | 29 +- lbtt/src/PathEvaluator.cc | 67 +- lbtt/src/PathEvaluator.h | 22 +- lbtt/src/PathIterator.cc | 8 +- lbtt/src/PathIterator.h | 8 +- lbtt/src/Product.h | 53 +- lbtt/src/ProductAutomaton.cc | 1063 +++++ lbtt/src/ProductAutomaton.h | 596 +++ lbtt/src/Random.h | 4 +- lbtt/src/SccCollection.h | 34 +- lbtt/src/SccIterator.h | 752 +++ lbtt/src/SharedTestData.h | 18 +- lbtt/src/SpinWrapper.cc | 8 +- lbtt/src/SpinWrapper.h | 8 +- lbtt/src/StatDisplay.cc | 598 ++- lbtt/src/StatDisplay.h | 49 +- lbtt/src/StateSpace.cc | 11 +- lbtt/src/StateSpace.h | 4 +- lbtt/src/StateSpaceProduct.h | 4 +- lbtt/src/StateSpaceRandomizer.cc | 8 +- lbtt/src/StateSpaceRandomizer.h | 8 +- lbtt/src/StringUtil.cc | 525 ++- lbtt/src/StringUtil.h | 176 +- lbtt/src/TempFsysName.cc | 4 +- lbtt/src/TempFsysName.h | 4 +- lbtt/src/TestOperations.cc | 1221 +++-- lbtt/src/TestOperations.h | 38 +- lbtt/src/TestRoundInfo.h | 51 +- lbtt/src/TestStatistics.cc | 13 +- lbtt/src/TestStatistics.h | 118 +- lbtt/src/TranslatorInterface.h | 4 +- lbtt/src/UserCommandReader.cc | 205 +- lbtt/src/UserCommandReader.h | 17 +- lbtt/src/UserCommands.cc | 1501 +++--- lbtt/src/UserCommands.h | 101 +- lbtt/src/main.cc | 434 +- lbtt/src/translate.cc | 10 +- lbtt/src/translate.h | 8 +- 77 files changed, 16272 insertions(+), 6019 deletions(-) create mode 100644 lbtt/doc/texinfo.tex create mode 100644 lbtt/src/ProductAutomaton.cc create mode 100644 lbtt/src/ProductAutomaton.h create mode 100644 lbtt/src/SccIterator.h diff --git a/lbtt/AUTHORS b/lbtt/AUTHORS index 87908cad0..5fd9b83c0 100644 --- a/lbtt/AUTHORS +++ b/lbtt/AUTHORS @@ -1 +1 @@ -lbtt was written by Heikki Tauriainen +Heikki Tauriainen diff --git a/lbtt/ChangeLog b/lbtt/ChangeLog index 02f78630f..3d865a4e8 100644 --- a/lbtt/ChangeLog +++ b/lbtt/ChangeLog @@ -16,13 +16,13 @@ Update description of the --formulafile command line option. (Configuration::print): Do not display a file name when reading formulas from standard input. - + * src/SpotWrapper.h, src/SpotWrapper.cc: Merge files from Spot 0.2 (contributed by Alexandre Duret-Lutz); remove #pragma definitions. * src/ExternalTranslator.h, src/Makefile.in, src/translate.cc: Merge changes from Spot 0.2 (contributed by Alexandre Duret-Lutz). - + * doc/lbtt.texi: Fix typo in URL of the FormulaOptions block generator. Update version, add documentation and references about support for Spot. Describe the new semantics of the --formulafile @@ -124,7 +124,7 @@ log file. * configure.ac: Add test for the setsid library function. - + 2004-07-31 Heikki Tauriainen * src/Product.h (ProductEdge::edge_1, ProductEdge::edge_2): @@ -178,7 +178,7 @@ * 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 @@ -257,13 +257,13 @@ 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. @@ -337,7 +337,7 @@ (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. @@ -365,7 +365,7 @@ * 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 @@ -475,7 +475,7 @@ 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. @@ -505,7 +505,7 @@ * 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) @@ -513,7 +513,7 @@ 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. @@ -858,7 +858,7 @@ 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. @@ -926,7 +926,7 @@ Reformat the automata file format section to avoid overfull lines in dvi generation. Fix description of the Algorithm block used with lbtt-translate. - + * doc/testprocedure.txt, doc/intersectioncheck.txt: Add initial newlines. diff --git a/lbtt/NEWS b/lbtt/NEWS index 1f3705dc8..1f63955f8 100644 --- a/lbtt/NEWS +++ b/lbtt/NEWS @@ -1,5 +1,5 @@ -lbtt NEWS -- history of user-visible changes. 01 Oct 2002 -Copyright (C) 2002 Heikki Tauriainen +lbtt NEWS -- history of user-visible changes. 30 Aug 2005 +Copyright (C) 2005 Heikki Tauriainen Permission is granted to anyone to make or distribute verbatim copies of this document as received, in any medium, provided that the @@ -10,7 +10,181 @@ Copyright (C) 2002 Heikki Tauriainen provided also that they carry prominent notices stating who last changed them. -Please send bug reports to . +Please send bug reports to . + +Version 1.2.0 + +* This release adds direct support (contributed by Alexandre Duret-Lutz) + for the LTL-to-Büchi translator distributed with the Spot model + checking library (available at ). + + lbtt 1.2.0 also supports reading input formulas from standard input + (by using the command-line option `--formulafile=-'; when reading input + formulas from an actual file, the filename needs to be different from + "-"). + +Version 1.1.3 + +* This release fixes build problems with GCC 4 and more job control + problems. + +Version 1.1.2 + +* Another bug fix release that fixes memory access and job control + problems. + +Version 1.1.1 + +* This release includes fixes to build problems with non-GNU + compilers on GNU libc systems and a few minor bug fixes. + +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 + and Darwin, and documentation generation in dvi format. A problem + with reading user commands from a source that is not a terminal was + also fixed. Many thanks to Alexandre Duret-Lutz for patches and + useful suggestions. + +Version 1.0.2 + +* Bug fix release. + +* The official WWW home page of the tool is now located at + . From there you can also + access the FormulaOptions block generator for lbtt configuration + files. The generator has limited support for specifying relative + (instead of absolute) priorities for the LTL operators. Version 1.0.1 diff --git a/lbtt/README b/lbtt/README index fcaf03f9f..3fdb6f194 100644 --- a/lbtt/README +++ b/lbtt/README @@ -1,24 +1,24 @@ -lbtt version 1.0.1 +lbtt version 1.2.0 ------------------ -lbtt is a tool for testing programs which translate formulas +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üchi 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 -. +. lbtt is free software, you may change and redistribute it under the terms of the GNU General Public License. lbtt comes with NO WARRANTY. See the file COPYING for details. -Quick installation instructions: +Brief installation instructions: -------------------------------- The basic procedure to build lbtt, the associated tools @@ -82,4 +82,4 @@ Documentation: The documentation is also available in various formats at the program's home page at - . + . diff --git a/lbtt/configure.ac b/lbtt/configure.ac index 5c9df77bc..5cc044e60 100644 --- a/lbtt/configure.ac +++ b/lbtt/configure.ac @@ -1,8 +1,8 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.59]) -AC_INIT([lbtt], [1.1.1], [heikki.tauriainen@hut.fi]) -AC_REVISION([Revision: 1.5]) +AC_INIT([lbtt], [1.2.0], [heikki.tauriainen@tkk.fi]) +AC_REVISION([Revision: 1.8]) AC_CONFIG_SRCDIR([src/main.cc]) AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE @@ -124,52 +124,6 @@ AC_CHECK_HEADERS( # Checks for typedefs, structures, and compiler characteristics. -# Check for the availability of the slist header (an extension to the C++ -# Standard Template Library). (In GCC 3.x the header is in the ext/ -# subdirectory of the directory containing the standard C++ headers.) - -AC_MSG_CHECKING([for slist]) -for slist_header in slist ext/slist no; do - if test "${slist_header}" != no; then - 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 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_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_SUBST([INCLUDE_SLIST_HEADER], ["#include <${slist_header}>"]) - else - slist_header=no - fi -fi - -if test "${slist_header}" = no; then - AC_MSG_RESULT([no]) -fi - AC_LANG(C) AC_CHECK_TYPES( @@ -191,7 +145,7 @@ AC_C_INLINE # Checks for library functions. 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], + [strchr strtod strtol strtoul strerror mkdir mkstemp open read write close popen pclose pipe fork execvp getpgrp setpgid tcgetpgrp tcsetpgrp 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]) diff --git a/lbtt/doc/intersectioncheck.txt b/lbtt/doc/intersectioncheck.txt index 405f12dce..c2b7fa447 100644 --- a/lbtt/doc/intersectioncheck.txt +++ b/lbtt/doc/intersectioncheck.txt @@ -1,3 +1,4 @@ + ,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,,,,, : LTL formula `f' :_____ : Negated LTL formula `!f' : '''''''T''''''T'''' \ ___'''''''T'''''''''''T'''''''' diff --git a/lbtt/doc/lbtt.texi b/lbtt/doc/lbtt.texi index ae5b65b91..06a9fd2f6 100644 --- a/lbtt/doc/lbtt.texi +++ b/lbtt/doc/lbtt.texi @@ -14,13 +14,13 @@ This file documents how to use the LTL-to-B@"uchi translator testbench @command{lbtt}. -Copyright @copyright{} 2004 Heikki Tauriainen +Copyright @copyright{} 2005 Heikki Tauriainen @ifinfo -@email{heikki.tauriainen@@hut.fi} +@email{heikki.tauriainen@@tkk.fi} @end ifinfo @ifnotinfo @ifnothtml -<@email{heikki.tauriainen@@hut.fi}> +<@email{heikki.tauriainen@@tkk.fi}> @end ifnothtml @end ifnotinfo @@ -64,12 +64,12 @@ 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 -@author Heikki Tauriainen <@email{heikki.tauriainen@@hut.fi}> +@subtitle @today, @command{lbtt} Versions 1.2.x +@author Heikki Tauriainen <@email{heikki.tauriainen@@tkk.fi}> @page @vskip 0pt plus 1filll -Copyright @copyright{} 2004 Heikki Tauriainen -<@email{heikki.tauriainen@@hut.fi}> +Copyright @copyright{} 2005 Heikki Tauriainen +<@email{heikki.tauriainen@@tkk.fi}> The latest version of this manual can be obtained from@* <@url{http://www.tcs.hut.fi/Software/lbtt/}>. @@ -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.2.0 of the @command{lbtt} documentation. This edition +applies to @command{lbtt} versions 1.2.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. } @@ -284,17 +299,17 @@ for more information. By default, all tests @command{lbtt} makes are based on randomly generated input. However, the LTL formulas used as input for the LTL-to-B@"uchi translators can be optionally given by the user by telling @command{lbtt} to -read LTL formulas from a file +read LTL formulas from a file or from standard input (@pxref{--formulafile,,@samp{--formulafile} command line option}). @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 @@ -2043,17 +2121,33 @@ file @samp{config} in the current working directory. @item @anchor{--formulafile}--formulafile=@var{FILE-NAME} @vindex --formulafile -@cindex LTL formula, reading from a file +@cindex LTL formula, reading from a file or standard input @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 (or standard input) instead of generating them randomly. The +special filename @samp{-} refers to standard input. Each +input formula should be followed by a newline. 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 +2168,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 +2179,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 +2208,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 +2226,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 +2235,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 +2249,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 +2273,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 +2309,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 +2350,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 +2429,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. @@ -2431,8 +2523,8 @@ This option sets the priority for the logical ``exclusive or'' operator. Note also the @samp{--formulafile=@var{FILE-NAME}} option (@pxref{--formulafile,,@samp{--formulafile} option}), which can be used to -instruct @command{lbtt} to read LTL formulas from a file instead of generating -them randomly. +instruct @command{lbtt} to read LTL formulas from a file (or standard input) +instead of generating them randomly. @@ -2510,6 +2602,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 +2621,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 +2642,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 @@ -2571,9 +2666,9 @@ Random state space generation parameters. @item Random LTL formula generation parameters (unless reading LTL formulas from -a file; @pxref{--formulafile,,@samp{--formulafile} command line option}). -This includes information about all enabled formula operators and their -priorities. When using the command line option +an external source; @pxref{--formulafile,,@samp{--formulafile} command line +option}). This includes information about all enabled formula operators and +their priorities. When using the command line option @samp{--showoperatordistribution} (@pxref{--showoperatordistribution,,@samp{--showoperatordistribution} option}), @command{lbtt} shows also the expected number of occurrence of each @@ -2590,6 +2685,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 +2693,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 +2715,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 +2803,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 +2825,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 +2832,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 +2846,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 +2871,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 +2905,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 +2974,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 +2995,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 +3035,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 +3048,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 +3070,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 +3091,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 +3112,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 +3172,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 +3201,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 +3219,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 +3250,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 +3274,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 +3298,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 +3319,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 +3333,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 +3343,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 +3354,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 +3368,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 +3378,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 +3401,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 +3440,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 +3488,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 +3507,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 +3600,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 +3633,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 +3747,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 +3848,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 +3876,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 +3936,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 +3981,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]}. @@ -3792,25 +4027,43 @@ See @end ifnotinfo for more information. +@item +@cindex Spot +@ifnottex +Spot @ref{[DP04]} +@end ifnottex +@iftex +Spot [DP04] +@end iftex +--- a model checking library that includes a module for translating LTL +formulas into B@"uchi automata incorporating optimization techniques from +several different sources. See +@ifinfo +@url{http://spot.lip6.fr/} +@end ifinfo +@ifnotinfo +<@uref{http://spot.lip6.fr/}> +@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 +4075,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 +source 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 +4095,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. @@ -3852,36 +4107,80 @@ International Conference on Computer Aided Verification (CAV'99)}, volume 1633 of @i{Lecture Notes in Computer Science}, pages 249---260. Springer-Verlag, 1999. +@item @anchor{[DP04]} [DP04] +A.@: Duret-Lutz and D.@: Poitrenaud. SPOT: An Extensible Model Checking Library +Using Transition-Based Generalized B@"uchi Automata. In +@i{Proceedings of the 12th IEEE/ACM International Symposium on Modeling, +Analysis, and Simulation of Computer and Telecommunication Systems +(MASCOTS 2004)}, pages 76--83. IEEE Computer Society Press, 2004. + @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 +4192,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 +4203,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 +4251,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 +4268,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 +4704,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 +4732,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 +4743,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 +4813,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 +4843,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 +4872,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 +4887,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 +4956,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 +4965,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 +5008,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 +5088,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 +5109,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 +5129,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/doc/testprocedure.txt b/lbtt/doc/testprocedure.txt index ec0b1d45e..de2e7edc4 100644 --- a/lbtt/doc/testprocedure.txt +++ b/lbtt/doc/testprocedure.txt @@ -1,3 +1,4 @@ + ,,,,,,,,,,,,,,, : State space : ''''''''''''''' diff --git a/lbtt/doc/texinfo.tex b/lbtt/doc/texinfo.tex new file mode 100644 index 000000000..808362235 --- /dev/null +++ b/lbtt/doc/texinfo.tex @@ -0,0 +1,7482 @@ +% texinfo.tex -- TeX macros to handle Texinfo files. +% +% Load plain if necessary, i.e., if running under initex. +\expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi +% +\def\texinfoversion{2006-10-04.17} +% +% Copyright (C) 1985, 1986, 1988, 1990, 1991, 1992, 1993, 1994, 1995, +% 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free +% Software Foundation, Inc. +% +% This texinfo.tex file 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, or (at +% your option) any later version. +% +% This texinfo.tex file 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 texinfo.tex file; see the file COPYING. If not, write +% to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +% Boston, MA 02110-1301, USA. +% +% As a special exception, when this file is read by TeX when processing +% a Texinfo source document, you may use the result without +% restriction. (This has been our intent since Texinfo was invented.) +% +% Please try the latest version of texinfo.tex before submitting bug +% reports; you can get the latest version from: +% http://www.gnu.org/software/texinfo/ (the Texinfo home page), or +% ftp://tug.org/tex/texinfo.tex +% (and all CTAN mirrors, see http://www.ctan.org). +% The texinfo.tex in any given distribution could well be out +% of date, so if that's what you're using, please check. +% +% Send bug reports to bug-texinfo@gnu.org. Please include including a +% complete document in each bug report with which we can reproduce the +% problem. Patches are, of course, greatly appreciated. +% +% To process a Texinfo manual with TeX, it's most reliable to use the +% texi2dvi shell script that comes with the distribution. For a simple +% manual foo.texi, however, you can get away with this: +% tex foo.texi +% texindex foo.?? +% tex foo.texi +% tex foo.texi +% dvips foo.dvi -o # or whatever; this makes foo.ps. +% The extra TeX runs get the cross-reference information correct. +% Sometimes one run after texindex suffices, and sometimes you need more +% than two; texi2dvi does it as many times as necessary. +% +% It is possible to adapt texinfo.tex for other languages, to some +% extent. You can get the existing language-specific files from the +% full Texinfo distribution. +% +% The GNU Texinfo home page is http://www.gnu.org/software/texinfo. + + +\message{Loading texinfo [version \texinfoversion]:} + +% If in a .fmt file, print the version number +% and turn on active characters that we couldn't do earlier because +% they might have appeared in the input file name. +\everyjob{\message{[Texinfo version \texinfoversion]}% + \catcode`+=\active \catcode`\_=\active} + +\message{Basics,} +\chardef\other=12 + +% We never want plain's \outer definition of \+ in Texinfo. +% For @tex, we can use \tabalign. +\let\+ = \relax + +% Save some plain tex macros whose names we will redefine. +\let\ptexb=\b +\let\ptexbullet=\bullet +\let\ptexc=\c +\let\ptexcomma=\, +\let\ptexdot=\. +\let\ptexdots=\dots +\let\ptexend=\end +\let\ptexequiv=\equiv +\let\ptexexclam=\! +\let\ptexfootnote=\footnote +\let\ptexgtr=> +\let\ptexhat=^ +\let\ptexi=\i +\let\ptexindent=\indent +\let\ptexinsert=\insert +\let\ptexlbrace=\{ +\let\ptexless=< +\let\ptexnewwrite\newwrite +\let\ptexnoindent=\noindent +\let\ptexplus=+ +\let\ptexrbrace=\} +\let\ptexslash=\/ +\let\ptexstar=\* +\let\ptext=\t + +% If this character appears in an error message or help string, it +% starts a new line in the output. +\newlinechar = `^^J + +% Use TeX 3.0's \inputlineno to get the line number, for better error +% messages, but if we're using an old version of TeX, don't do anything. +% +\ifx\inputlineno\thisisundefined + \let\linenumber = \empty % Pre-3.0. +\else + \def\linenumber{l.\the\inputlineno:\space} +\fi + +% Set up fixed words for English if not already set. +\ifx\putwordAppendix\undefined \gdef\putwordAppendix{Appendix}\fi +\ifx\putwordChapter\undefined \gdef\putwordChapter{Chapter}\fi +\ifx\putwordfile\undefined \gdef\putwordfile{file}\fi +\ifx\putwordin\undefined \gdef\putwordin{in}\fi +\ifx\putwordIndexIsEmpty\undefined \gdef\putwordIndexIsEmpty{(Index is empty)}\fi +\ifx\putwordIndexNonexistent\undefined \gdef\putwordIndexNonexistent{(Index is nonexistent)}\fi +\ifx\putwordInfo\undefined \gdef\putwordInfo{Info}\fi +\ifx\putwordInstanceVariableof\undefined \gdef\putwordInstanceVariableof{Instance Variable of}\fi +\ifx\putwordMethodon\undefined \gdef\putwordMethodon{Method on}\fi +\ifx\putwordNoTitle\undefined \gdef\putwordNoTitle{No Title}\fi +\ifx\putwordof\undefined \gdef\putwordof{of}\fi +\ifx\putwordon\undefined \gdef\putwordon{on}\fi +\ifx\putwordpage\undefined \gdef\putwordpage{page}\fi +\ifx\putwordsection\undefined \gdef\putwordsection{section}\fi +\ifx\putwordSection\undefined \gdef\putwordSection{Section}\fi +\ifx\putwordsee\undefined \gdef\putwordsee{see}\fi +\ifx\putwordSee\undefined \gdef\putwordSee{See}\fi +\ifx\putwordShortTOC\undefined \gdef\putwordShortTOC{Short Contents}\fi +\ifx\putwordTOC\undefined \gdef\putwordTOC{Table of Contents}\fi +% +\ifx\putwordMJan\undefined \gdef\putwordMJan{January}\fi +\ifx\putwordMFeb\undefined \gdef\putwordMFeb{February}\fi +\ifx\putwordMMar\undefined \gdef\putwordMMar{March}\fi +\ifx\putwordMApr\undefined \gdef\putwordMApr{April}\fi +\ifx\putwordMMay\undefined \gdef\putwordMMay{May}\fi +\ifx\putwordMJun\undefined \gdef\putwordMJun{June}\fi +\ifx\putwordMJul\undefined \gdef\putwordMJul{July}\fi +\ifx\putwordMAug\undefined \gdef\putwordMAug{August}\fi +\ifx\putwordMSep\undefined \gdef\putwordMSep{September}\fi +\ifx\putwordMOct\undefined \gdef\putwordMOct{October}\fi +\ifx\putwordMNov\undefined \gdef\putwordMNov{November}\fi +\ifx\putwordMDec\undefined \gdef\putwordMDec{December}\fi +% +\ifx\putwordDefmac\undefined \gdef\putwordDefmac{Macro}\fi +\ifx\putwordDefspec\undefined \gdef\putwordDefspec{Special Form}\fi +\ifx\putwordDefvar\undefined \gdef\putwordDefvar{Variable}\fi +\ifx\putwordDefopt\undefined \gdef\putwordDefopt{User Option}\fi +\ifx\putwordDeffunc\undefined \gdef\putwordDeffunc{Function}\fi + +% Since the category of space is not known, we have to be careful. +\chardef\spacecat = 10 +\def\spaceisspace{\catcode`\ =\spacecat} + +% sometimes characters are active, so we need control sequences. +\chardef\colonChar = `\: +\chardef\commaChar = `\, +\chardef\dashChar = `\- +\chardef\dotChar = `\. +\chardef\exclamChar= `\! +\chardef\lquoteChar= `\` +\chardef\questChar = `\? +\chardef\rquoteChar= `\' +\chardef\semiChar = `\; +\chardef\underChar = `\_ + +% Ignore a token. +% +\def\gobble#1{} + +% The following is used inside several \edef's. +\def\makecsname#1{\expandafter\noexpand\csname#1\endcsname} + +% Hyphenation fixes. +\hyphenation{ + Flor-i-da Ghost-script Ghost-view Mac-OS Post-Script + ap-pen-dix bit-map bit-maps + data-base data-bases eshell fall-ing half-way long-est man-u-script + man-u-scripts mini-buf-fer mini-buf-fers over-view par-a-digm + par-a-digms rath-er rec-tan-gu-lar ro-bot-ics se-vere-ly set-up spa-ces + spell-ing spell-ings + stand-alone strong-est time-stamp time-stamps which-ever white-space + wide-spread wrap-around +} + +% Margin to add to right of even pages, to left of odd pages. +\newdimen\bindingoffset +\newdimen\normaloffset +\newdimen\pagewidth \newdimen\pageheight + +% For a final copy, take out the rectangles +% that mark overfull boxes (in case you have decided +% that the text looks ok even though it passes the margin). +% +\def\finalout{\overfullrule=0pt} + +% @| inserts a changebar to the left of the current line. It should +% surround any changed text. This approach does *not* work if the +% change spans more than two lines of output. To handle that, we would +% have adopt a much more difficult approach (putting marks into the main +% vertical list for the beginning and end of each change). +% +\def\|{% + % \vadjust can only be used in horizontal mode. + \leavevmode + % + % Append this vertical mode material after the current line in the output. + \vadjust{% + % We want to insert a rule with the height and depth of the current + % leading; that is exactly what \strutbox is supposed to record. + \vskip-\baselineskip + % + % \vadjust-items are inserted at the left edge of the type. So + % the \llap here moves out into the left-hand margin. + \llap{% + % + % For a thicker or thinner bar, change the `1pt'. + \vrule height\baselineskip width1pt + % + % This is the space between the bar and the text. + \hskip 12pt + }% + }% +} + +% Sometimes it is convenient to have everything in the transcript file +% and nothing on the terminal. We don't just call \tracingall here, +% since that produces some useless output on the terminal. We also make +% some effort to order the tracing commands to reduce output in the log +% file; cf. trace.sty in LaTeX. +% +\def\gloggingall{\begingroup \globaldefs = 1 \loggingall \endgroup}% +\def\loggingall{% + \tracingstats2 + \tracingpages1 + \tracinglostchars2 % 2 gives us more in etex + \tracingparagraphs1 + \tracingoutput1 + \tracingmacros2 + \tracingrestores1 + \showboxbreadth\maxdimen \showboxdepth\maxdimen + \ifx\eTeXversion\undefined\else % etex gives us more logging + \tracingscantokens1 + \tracingifs1 + \tracinggroups1 + \tracingnesting2 + \tracingassigns1 + \fi + \tracingcommands3 % 3 gives us more in etex + \errorcontextlines16 +}% + +% add check for \lastpenalty to plain's definitions. If the last thing +% we did was a \nobreak, we don't want to insert more space. +% +\def\smallbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\smallskipamount + \removelastskip\penalty-50\smallskip\fi\fi} +\def\medbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\medskipamount + \removelastskip\penalty-100\medskip\fi\fi} +\def\bigbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\bigskipamount + \removelastskip\penalty-200\bigskip\fi\fi} + +% For @cropmarks command. +% Do @cropmarks to get crop marks. +% +\newif\ifcropmarks +\let\cropmarks = \cropmarkstrue +% +% Dimensions to add cropmarks at corners. +% Added by P. A. MacKay, 12 Nov. 1986 +% +\newdimen\outerhsize \newdimen\outervsize % set by the paper size routines +\newdimen\cornerlong \cornerlong=1pc +\newdimen\cornerthick \cornerthick=.3pt +\newdimen\topandbottommargin \topandbottommargin=.75in + +% Main output routine. +\chardef\PAGE = 255 +\output = {\onepageout{\pagecontents\PAGE}} + +\newbox\headlinebox +\newbox\footlinebox + +% \onepageout takes a vbox as an argument. Note that \pagecontents +% does insertions, but you have to call it yourself. +\def\onepageout#1{% + \ifcropmarks \hoffset=0pt \else \hoffset=\normaloffset \fi + % + \ifodd\pageno \advance\hoffset by \bindingoffset + \else \advance\hoffset by -\bindingoffset\fi + % + % Do this outside of the \shipout so @code etc. will be expanded in + % the headline as they should be, not taken literally (outputting ''code). + \setbox\headlinebox = \vbox{\let\hsize=\pagewidth \makeheadline}% + \setbox\footlinebox = \vbox{\let\hsize=\pagewidth \makefootline}% + % + {% + % Have to do this stuff outside the \shipout because we want it to + % take effect in \write's, yet the group defined by the \vbox ends + % before the \shipout runs. + % + \indexdummies % don't expand commands in the output. + \normalturnoffactive % \ in index entries must not stay \, e.g., if + % the page break happens to be in the middle of an example. + % We don't want .vr (or whatever) entries like this: + % \entry{{\tt \indexbackslash }acronym}{32}{\code {\acronym}} + % "\acronym" won't work when it's read back in; + % it needs to be + % {\code {{\tt \backslashcurfont }acronym} + \shipout\vbox{% + % Do this early so pdf references go to the beginning of the page. + \ifpdfmakepagedest \pdfdest name{\the\pageno} xyz\fi + % + \ifcropmarks \vbox to \outervsize\bgroup + \hsize = \outerhsize + \vskip-\topandbottommargin + \vtop to0pt{% + \line{\ewtop\hfil\ewtop}% + \nointerlineskip + \line{% + \vbox{\moveleft\cornerthick\nstop}% + \hfill + \vbox{\moveright\cornerthick\nstop}% + }% + \vss}% + \vskip\topandbottommargin + \line\bgroup + \hfil % center the page within the outer (page) hsize. + \ifodd\pageno\hskip\bindingoffset\fi + \vbox\bgroup + \fi + % + \unvbox\headlinebox + \pagebody{#1}% + \ifdim\ht\footlinebox > 0pt + % Only leave this space if the footline is nonempty. + % (We lessened \vsize for it in \oddfootingyyy.) + % The \baselineskip=24pt in plain's \makefootline has no effect. + \vskip 24pt + \unvbox\footlinebox + \fi + % + \ifcropmarks + \egroup % end of \vbox\bgroup + \hfil\egroup % end of (centering) \line\bgroup + \vskip\topandbottommargin plus1fill minus1fill + \boxmaxdepth = \cornerthick + \vbox to0pt{\vss + \line{% + \vbox{\moveleft\cornerthick\nsbot}% + \hfill + \vbox{\moveright\cornerthick\nsbot}% + }% + \nointerlineskip + \line{\ewbot\hfil\ewbot}% + }% + \egroup % \vbox from first cropmarks clause + \fi + }% end of \shipout\vbox + }% end of group with \indexdummies + \advancepageno + \ifnum\outputpenalty>-20000 \else\dosupereject\fi +} + +\newinsert\margin \dimen\margin=\maxdimen + +\def\pagebody#1{\vbox to\pageheight{\boxmaxdepth=\maxdepth #1}} +{\catcode`\@ =11 +\gdef\pagecontents#1{\ifvoid\topins\else\unvbox\topins\fi +% marginal hacks, juha@viisa.uucp (Juha Takala) +\ifvoid\margin\else % marginal info is present + \rlap{\kern\hsize\vbox to\z@{\kern1pt\box\margin \vss}}\fi +\dimen@=\dp#1 \unvbox#1 +\ifvoid\footins\else\vskip\skip\footins\footnoterule \unvbox\footins\fi +\ifr@ggedbottom \kern-\dimen@ \vfil \fi} +} + +% Here are the rules for the cropmarks. Note that they are +% offset so that the space between them is truly \outerhsize or \outervsize +% (P. A. MacKay, 12 November, 1986) +% +\def\ewtop{\vrule height\cornerthick depth0pt width\cornerlong} +\def\nstop{\vbox + {\hrule height\cornerthick depth\cornerlong width\cornerthick}} +\def\ewbot{\vrule height0pt depth\cornerthick width\cornerlong} +\def\nsbot{\vbox + {\hrule height\cornerlong depth\cornerthick width\cornerthick}} + +% Parse an argument, then pass it to #1. The argument is the rest of +% the input line (except we remove a trailing comment). #1 should be a +% macro which expects an ordinary undelimited TeX argument. +% +\def\parsearg{\parseargusing{}} +\def\parseargusing#1#2{% + \def\argtorun{#2}% + \begingroup + \obeylines + \spaceisspace + #1% + \parseargline\empty% Insert the \empty token, see \finishparsearg below. +} + +{\obeylines % + \gdef\parseargline#1^^M{% + \endgroup % End of the group started in \parsearg. + \argremovecomment #1\comment\ArgTerm% + }% +} + +% First remove any @comment, then any @c comment. +\def\argremovecomment#1\comment#2\ArgTerm{\argremovec #1\c\ArgTerm} +\def\argremovec#1\c#2\ArgTerm{\argcheckspaces#1\^^M\ArgTerm} + +% Each occurence of `\^^M' or `\^^M' is replaced by a single space. +% +% \argremovec might leave us with trailing space, e.g., +% @end itemize @c foo +% This space token undergoes the same procedure and is eventually removed +% by \finishparsearg. +% +\def\argcheckspaces#1\^^M{\argcheckspacesX#1\^^M \^^M} +\def\argcheckspacesX#1 \^^M{\argcheckspacesY#1\^^M} +\def\argcheckspacesY#1\^^M#2\^^M#3\ArgTerm{% + \def\temp{#3}% + \ifx\temp\empty + % Do not use \next, perhaps the caller of \parsearg uses it; reuse \temp: + \let\temp\finishparsearg + \else + \let\temp\argcheckspaces + \fi + % Put the space token in: + \temp#1 #3\ArgTerm +} + +% If a _delimited_ argument is enclosed in braces, they get stripped; so +% to get _exactly_ the rest of the line, we had to prevent such situation. +% We prepended an \empty token at the very beginning and we expand it now, +% just before passing the control to \argtorun. +% (Similarily, we have to think about #3 of \argcheckspacesY above: it is +% either the null string, or it ends with \^^M---thus there is no danger +% that a pair of braces would be stripped. +% +% But first, we have to remove the trailing space token. +% +\def\finishparsearg#1 \ArgTerm{\expandafter\argtorun\expandafter{#1}} + +% \parseargdef\foo{...} +% is roughly equivalent to +% \def\foo{\parsearg\Xfoo} +% \def\Xfoo#1{...} +% +% Actually, I use \csname\string\foo\endcsname, ie. \\foo, as it is my +% favourite TeX trick. --kasal, 16nov03 + +\def\parseargdef#1{% + \expandafter \doparseargdef \csname\string#1\endcsname #1% +} +\def\doparseargdef#1#2{% + \def#2{\parsearg#1}% + \def#1##1% +} + +% Several utility definitions with active space: +{ + \obeyspaces + \gdef\obeyedspace{ } + + % Make each space character in the input produce a normal interword + % space in the output. Don't allow a line break at this space, as this + % is used only in environments like @example, where each line of input + % should produce a line of output anyway. + % + \gdef\sepspaces{\obeyspaces\let =\tie} + + % If an index command is used in an @example environment, any spaces + % therein should become regular spaces in the raw index file, not the + % expansion of \tie (\leavevmode \penalty \@M \ ). + \gdef\unsepspaces{\let =\space} +} + + +\def\flushcr{\ifx\par\lisppar \def\next##1{}\else \let\next=\relax \fi \next} + +% Define the framework for environments in texinfo.tex. It's used like this: +% +% \envdef\foo{...} +% \def\Efoo{...} +% +% It's the responsibility of \envdef to insert \begingroup before the +% actual body; @end closes the group after calling \Efoo. \envdef also +% defines \thisenv, so the current environment is known; @end checks +% whether the environment name matches. The \checkenv macro can also be +% used to check whether the current environment is the one expected. +% +% Non-false conditionals (@iftex, @ifset) don't fit into this, so they +% are not treated as enviroments; they don't open a group. (The +% implementation of @end takes care not to call \endgroup in this +% special case.) + + +% At runtime, environments start with this: +\def\startenvironment#1{\begingroup\def\thisenv{#1}} +% initialize +\let\thisenv\empty + +% ... but they get defined via ``\envdef\foo{...}'': +\long\def\envdef#1#2{\def#1{\startenvironment#1#2}} +\def\envparseargdef#1#2{\parseargdef#1{\startenvironment#1#2}} + +% Check whether we're in the right environment: +\def\checkenv#1{% + \def\temp{#1}% + \ifx\thisenv\temp + \else + \badenverr + \fi +} + +% Evironment mismatch, #1 expected: +\def\badenverr{% + \errhelp = \EMsimple + \errmessage{This command can appear only \inenvironment\temp, + not \inenvironment\thisenv}% +} +\def\inenvironment#1{% + \ifx#1\empty + out of any environment% + \else + in environment \expandafter\string#1% + \fi +} + +% @end foo executes the definition of \Efoo. +% But first, it executes a specialized version of \checkenv +% +\parseargdef\end{% + \if 1\csname iscond.#1\endcsname + \else + % The general wording of \badenverr may not be ideal, but... --kasal, 06nov03 + \expandafter\checkenv\csname#1\endcsname + \csname E#1\endcsname + \endgroup + \fi +} + +\newhelp\EMsimple{Press RETURN to continue.} + + +%% Simple single-character @ commands + +% @@ prints an @ +% Kludge this until the fonts are right (grr). +\def\@{{\tt\char64}} + +% This is turned off because it was never documented +% and you can use @w{...} around a quote to suppress ligatures. +%% Define @` and @' to be the same as ` and ' +%% but suppressing ligatures. +%\def\`{{`}} +%\def\'{{'}} + +% Used to generate quoted braces. +\def\mylbrace {{\tt\char123}} +\def\myrbrace {{\tt\char125}} +\let\{=\mylbrace +\let\}=\myrbrace +\begingroup + % Definitions to produce \{ and \} commands for indices, + % and @{ and @} for the aux/toc files. + \catcode`\{ = \other \catcode`\} = \other + \catcode`\[ = 1 \catcode`\] = 2 + \catcode`\! = 0 \catcode`\\ = \other + !gdef!lbracecmd[\{]% + !gdef!rbracecmd[\}]% + !gdef!lbraceatcmd[@{]% + !gdef!rbraceatcmd[@}]% +!endgroup + +% @comma{} to avoid , parsing problems. +\let\comma = , + +% Accents: @, @dotaccent @ringaccent @ubaraccent @udotaccent +% Others are defined by plain TeX: @` @' @" @^ @~ @= @u @v @H. +\let\, = \c +\let\dotaccent = \. +\def\ringaccent#1{{\accent23 #1}} +\let\tieaccent = \t +\let\ubaraccent = \b +\let\udotaccent = \d + +% Other special characters: @questiondown @exclamdown @ordf @ordm +% Plain TeX defines: @AA @AE @O @OE @L (plus lowercase versions) @ss. +\def\questiondown{?`} +\def\exclamdown{!`} +\def\ordf{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{a}}} +\def\ordm{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{o}}} + +% Dotless i and dotless j, used for accents. +\def\imacro{i} +\def\jmacro{j} +\def\dotless#1{% + \def\temp{#1}% + \ifx\temp\imacro \ptexi + \else\ifx\temp\jmacro \j + \else \errmessage{@dotless can be used only with i or j}% + \fi\fi +} + +% The \TeX{} logo, as in plain, but resetting the spacing so that a +% period following counts as ending a sentence. (Idea found in latex.) +% +\edef\TeX{\TeX \spacefactor=1000 } + +% @LaTeX{} logo. Not quite the same results as the definition in +% latex.ltx, since we use a different font for the raised A; it's most +% convenient for us to use an explicitly smaller font, rather than using +% the \scriptstyle font (since we don't reset \scriptstyle and +% \scriptscriptstyle). +% +\def\LaTeX{% + L\kern-.36em + {\setbox0=\hbox{T}% + \vbox to \ht0{\hbox{\selectfonts\lllsize A}\vss}}% + \kern-.15em + \TeX +} + +% Be sure we're in horizontal mode when doing a tie, since we make space +% equivalent to this in @example-like environments. Otherwise, a space +% at the beginning of a line will start with \penalty -- and +% since \penalty is valid in vertical mode, we'd end up putting the +% penalty on the vertical list instead of in the new paragraph. +{\catcode`@ = 11 + % Avoid using \@M directly, because that causes trouble + % if the definition is written into an index file. + \global\let\tiepenalty = \@M + \gdef\tie{\leavevmode\penalty\tiepenalty\ } +} + +% @: forces normal size whitespace following. +\def\:{\spacefactor=1000 } + +% @* forces a line break. +\def\*{\hfil\break\hbox{}\ignorespaces} + +% @/ allows a line break. +\let\/=\allowbreak + +% @. is an end-of-sentence period. +\def\.{.\spacefactor=\endofsentencespacefactor\space} + +% @! is an end-of-sentence bang. +\def\!{!\spacefactor=\endofsentencespacefactor\space} + +% @? is an end-of-sentence query. +\def\?{?\spacefactor=\endofsentencespacefactor\space} + +% @frenchspacing on|off says whether to put extra space after punctuation. +% +\def\onword{on} +\def\offword{off} +% +\parseargdef\frenchspacing{% + \def\temp{#1}% + \ifx\temp\onword \plainfrenchspacing + \else\ifx\temp\offword \plainnonfrenchspacing + \else + \errhelp = \EMsimple + \errmessage{Unknown @frenchspacing option `\temp', must be on/off}% + \fi\fi +} + +% @w prevents a word break. Without the \leavevmode, @w at the +% beginning of a paragraph, when TeX is still in vertical mode, would +% produce a whole line of output instead of starting the paragraph. +\def\w#1{\leavevmode\hbox{#1}} + +% @group ... @end group forces ... to be all on one page, by enclosing +% it in a TeX vbox. We use \vtop instead of \vbox to construct the box +% to keep its height that of a normal line. According to the rules for +% \topskip (p.114 of the TeXbook), the glue inserted is +% max (\topskip - \ht (first item), 0). If that height is large, +% therefore, no glue is inserted, and the space between the headline and +% the text is small, which looks bad. +% +% Another complication is that the group might be very large. This can +% cause the glue on the previous page to be unduly stretched, because it +% does not have much material. In this case, it's better to add an +% explicit \vfill so that the extra space is at the bottom. The +% threshold for doing this is if the group is more than \vfilllimit +% percent of a page (\vfilllimit can be changed inside of @tex). +% +\newbox\groupbox +\def\vfilllimit{0.7} +% +\envdef\group{% + \ifnum\catcode`\^^M=\active \else + \errhelp = \groupinvalidhelp + \errmessage{@group invalid in context where filling is enabled}% + \fi + \startsavinginserts + % + \setbox\groupbox = \vtop\bgroup + % Do @comment since we are called inside an environment such as + % @example, where each end-of-line in the input causes an + % end-of-line in the output. We don't want the end-of-line after + % the `@group' to put extra space in the output. Since @group + % should appear on a line by itself (according to the Texinfo + % manual), we don't worry about eating any user text. + \comment +} +% +% The \vtop produces a box with normal height and large depth; thus, TeX puts +% \baselineskip glue before it, and (when the next line of text is done) +% \lineskip glue after it. Thus, space below is not quite equal to space +% above. But it's pretty close. +\def\Egroup{% + % To get correct interline space between the last line of the group + % and the first line afterwards, we have to propagate \prevdepth. + \endgraf % Not \par, as it may have been set to \lisppar. + \global\dimen1 = \prevdepth + \egroup % End the \vtop. + % \dimen0 is the vertical size of the group's box. + \dimen0 = \ht\groupbox \advance\dimen0 by \dp\groupbox + % \dimen2 is how much space is left on the page (more or less). + \dimen2 = \pageheight \advance\dimen2 by -\pagetotal + % if the group doesn't fit on the current page, and it's a big big + % group, force a page break. + \ifdim \dimen0 > \dimen2 + \ifdim \pagetotal < \vfilllimit\pageheight + \page + \fi + \fi + \box\groupbox + \prevdepth = \dimen1 + \checkinserts +} +% +% TeX puts in an \escapechar (i.e., `@') at the beginning of the help +% message, so this ends up printing `@group can only ...'. +% +\newhelp\groupinvalidhelp{% +group can only be used in environments such as @example,^^J% +where each line of input produces a line of output.} + +% @need space-in-mils +% forces a page break if there is not space-in-mils remaining. + +\newdimen\mil \mil=0.001in + +% Old definition--didn't work. +%\parseargdef\need{\par % +%% This method tries to make TeX break the page naturally +%% if the depth of the box does not fit. +%{\baselineskip=0pt% +%\vtop to #1\mil{\vfil}\kern -#1\mil\nobreak +%\prevdepth=-1000pt +%}} + +\parseargdef\need{% + % Ensure vertical mode, so we don't make a big box in the middle of a + % paragraph. + \par + % + % If the @need value is less than one line space, it's useless. + \dimen0 = #1\mil + \dimen2 = \ht\strutbox + \advance\dimen2 by \dp\strutbox + \ifdim\dimen0 > \dimen2 + % + % Do a \strut just to make the height of this box be normal, so the + % normal leading is inserted relative to the preceding line. + % And a page break here is fine. + \vtop to #1\mil{\strut\vfil}% + % + % TeX does not even consider page breaks if a penalty added to the + % main vertical list is 10000 or more. But in order to see if the + % empty box we just added fits on the page, we must make it consider + % page breaks. On the other hand, we don't want to actually break the + % page after the empty box. So we use a penalty of 9999. + % + % There is an extremely small chance that TeX will actually break the + % page at this \penalty, if there are no other feasible breakpoints in + % sight. (If the user is using lots of big @group commands, which + % almost-but-not-quite fill up a page, TeX will have a hard time doing + % good page breaking, for example.) However, I could not construct an + % example where a page broke at this \penalty; if it happens in a real + % document, then we can reconsider our strategy. + \penalty9999 + % + % Back up by the size of the box, whether we did a page break or not. + \kern -#1\mil + % + % Do not allow a page break right after this kern. + \nobreak + \fi +} + +% @br forces paragraph break (and is undocumented). + +\let\br = \par + +% @page forces the start of a new page. +% +\def\page{\par\vfill\supereject} + +% @exdent text.... +% outputs text on separate line in roman font, starting at standard page margin + +% This records the amount of indent in the innermost environment. +% That's how much \exdent should take out. +\newskip\exdentamount + +% This defn is used inside fill environments such as @defun. +\parseargdef\exdent{\hfil\break\hbox{\kern -\exdentamount{\rm#1}}\hfil\break} + +% This defn is used inside nofill environments such as @example. +\parseargdef\nofillexdent{{\advance \leftskip by -\exdentamount + \leftline{\hskip\leftskip{\rm#1}}}} + +% @inmargin{WHICH}{TEXT} puts TEXT in the WHICH margin next to the current +% paragraph. For more general purposes, use the \margin insertion +% class. WHICH is `l' or `r'. +% +\newskip\inmarginspacing \inmarginspacing=1cm +\def\strutdepth{\dp\strutbox} +% +\def\doinmargin#1#2{\strut\vadjust{% + \nobreak + \kern-\strutdepth + \vtop to \strutdepth{% + \baselineskip=\strutdepth + \vss + % if you have multiple lines of stuff to put here, you'll need to + % make the vbox yourself of the appropriate size. + \ifx#1l% + \llap{\ignorespaces #2\hskip\inmarginspacing}% + \else + \rlap{\hskip\hsize \hskip\inmarginspacing \ignorespaces #2}% + \fi + \null + }% +}} +\def\inleftmargin{\doinmargin l} +\def\inrightmargin{\doinmargin r} +% +% @inmargin{TEXT [, RIGHT-TEXT]} +% (if RIGHT-TEXT is given, use TEXT for left page, RIGHT-TEXT for right; +% else use TEXT for both). +% +\def\inmargin#1{\parseinmargin #1,,\finish} +\def\parseinmargin#1,#2,#3\finish{% not perfect, but better than nothing. + \setbox0 = \hbox{\ignorespaces #2}% + \ifdim\wd0 > 0pt + \def\lefttext{#1}% have both texts + \def\righttext{#2}% + \else + \def\lefttext{#1}% have only one text + \def\righttext{#1}% + \fi + % + \ifodd\pageno + \def\temp{\inrightmargin\righttext}% odd page -> outside is right margin + \else + \def\temp{\inleftmargin\lefttext}% + \fi + \temp +} + +% @include file insert text of that file as input. +% +\def\include{\parseargusing\filenamecatcodes\includezzz} +\def\includezzz#1{% + \pushthisfilestack + \def\thisfile{#1}% + {% + \makevalueexpandable + \def\temp{\input #1 }% + \expandafter + }\temp + \popthisfilestack +} +\def\filenamecatcodes{% + \catcode`\\=\other + \catcode`~=\other + \catcode`^=\other + \catcode`_=\other + \catcode`|=\other + \catcode`<=\other + \catcode`>=\other + \catcode`+=\other + \catcode`-=\other +} + +\def\pushthisfilestack{% + \expandafter\pushthisfilestackX\popthisfilestack\StackTerm +} +\def\pushthisfilestackX{% + \expandafter\pushthisfilestackY\thisfile\StackTerm +} +\def\pushthisfilestackY #1\StackTerm #2\StackTerm {% + \gdef\popthisfilestack{\gdef\thisfile{#1}\gdef\popthisfilestack{#2}}% +} + +\def\popthisfilestack{\errthisfilestackempty} +\def\errthisfilestackempty{\errmessage{Internal error: + the stack of filenames is empty.}} + +\def\thisfile{} + +% @center line +% outputs that line, centered. +% +\parseargdef\center{% + \ifhmode + \let\next\centerH + \else + \let\next\centerV + \fi + \next{\hfil \ignorespaces#1\unskip \hfil}% +} +\def\centerH#1{% + {% + \hfil\break + \advance\hsize by -\leftskip + \advance\hsize by -\rightskip + \line{#1}% + \break + }% +} +\def\centerV#1{\line{\kern\leftskip #1\kern\rightskip}} + +% @sp n outputs n lines of vertical space + +\parseargdef\sp{\vskip #1\baselineskip} + +% @comment ...line which is ignored... +% @c is the same as @comment +% @ignore ... @end ignore is another way to write a comment + +\def\comment{\begingroup \catcode`\^^M=\other% +\catcode`\@=\other \catcode`\{=\other \catcode`\}=\other% +\commentxxx} +{\catcode`\^^M=\other \gdef\commentxxx#1^^M{\endgroup}} + +\let\c=\comment + +% @paragraphindent NCHARS +% We'll use ems for NCHARS, close enough. +% NCHARS can also be the word `asis' or `none'. +% We cannot feasibly implement @paragraphindent asis, though. +% +\def\asisword{asis} % no translation, these are keywords +\def\noneword{none} +% +\parseargdef\paragraphindent{% + \def\temp{#1}% + \ifx\temp\asisword + \else + \ifx\temp\noneword + \defaultparindent = 0pt + \else + \defaultparindent = #1em + \fi + \fi + \parindent = \defaultparindent +} + +% @exampleindent NCHARS +% We'll use ems for NCHARS like @paragraphindent. +% It seems @exampleindent asis isn't necessary, but +% I preserve it to make it similar to @paragraphindent. +\parseargdef\exampleindent{% + \def\temp{#1}% + \ifx\temp\asisword + \else + \ifx\temp\noneword + \lispnarrowing = 0pt + \else + \lispnarrowing = #1em + \fi + \fi +} + +% @firstparagraphindent WORD +% If WORD is `none', then suppress indentation of the first paragraph +% after a section heading. If WORD is `insert', then do indent at such +% paragraphs. +% +% The paragraph indentation is suppressed or not by calling +% \suppressfirstparagraphindent, which the sectioning commands do. +% We switch the definition of this back and forth according to WORD. +% By default, we suppress indentation. +% +\def\suppressfirstparagraphindent{\dosuppressfirstparagraphindent} +\def\insertword{insert} +% +\parseargdef\firstparagraphindent{% + \def\temp{#1}% + \ifx\temp\noneword + \let\suppressfirstparagraphindent = \dosuppressfirstparagraphindent + \else\ifx\temp\insertword + \let\suppressfirstparagraphindent = \relax + \else + \errhelp = \EMsimple + \errmessage{Unknown @firstparagraphindent option `\temp'}% + \fi\fi +} + +% Here is how we actually suppress indentation. Redefine \everypar to +% \kern backwards by \parindent, and then reset itself to empty. +% +% We also make \indent itself not actually do anything until the next +% paragraph. +% +\gdef\dosuppressfirstparagraphindent{% + \gdef\indent{% + \restorefirstparagraphindent + \indent + }% + \gdef\noindent{% + \restorefirstparagraphindent + \noindent + }% + \global\everypar = {% + \kern -\parindent + \restorefirstparagraphindent + }% +} + +\gdef\restorefirstparagraphindent{% + \global \let \indent = \ptexindent + \global \let \noindent = \ptexnoindent + \global \everypar = {}% +} + + +% @asis just yields its argument. Used with @table, for example. +% +\def\asis#1{#1} + +% @math outputs its argument in math mode. +% +% One complication: _ usually means subscripts, but it could also mean +% an actual _ character, as in @math{@var{some_variable} + 1}. So make +% _ active, and distinguish by seeing if the current family is \slfam, +% which is what @var uses. +{ + \catcode`\_ = \active + \gdef\mathunderscore{% + \catcode`\_=\active + \def_{\ifnum\fam=\slfam \_\else\sb\fi}% + } +} +% Another complication: we want \\ (and @\) to output a \ character. +% FYI, plain.tex uses \\ as a temporary control sequence (why?), but +% this is not advertised and we don't care. Texinfo does not +% otherwise define @\. +% +% The \mathchar is class=0=ordinary, family=7=ttfam, position=5C=\. +\def\mathbackslash{\ifnum\fam=\ttfam \mathchar"075C \else\backslash \fi} +% +\def\math{% + \tex + \mathunderscore + \let\\ = \mathbackslash + \mathactive + $\finishmath +} +\def\finishmath#1{#1$\endgroup} % Close the group opened by \tex. + +% Some active characters (such as <) are spaced differently in math. +% We have to reset their definitions in case the @math was an argument +% to a command which sets the catcodes (such as @item or @section). +% +{ + \catcode`^ = \active + \catcode`< = \active + \catcode`> = \active + \catcode`+ = \active + \gdef\mathactive{% + \let^ = \ptexhat + \let< = \ptexless + \let> = \ptexgtr + \let+ = \ptexplus + } +} + +% @bullet and @minus need the same treatment as @math, just above. +\def\bullet{$\ptexbullet$} +\def\minus{$-$} + +% @dots{} outputs an ellipsis using the current font. +% We do .5em per period so that it has the same spacing in the cm +% typewriter fonts as three actual period characters; on the other hand, +% in other typewriter fonts three periods are wider than 1.5em. So do +% whichever is larger. +% +\def\dots{% + \leavevmode + \setbox0=\hbox{...}% get width of three periods + \ifdim\wd0 > 1.5em + \dimen0 = \wd0 + \else + \dimen0 = 1.5em + \fi + \hbox to \dimen0{% + \hskip 0pt plus.25fil + .\hskip 0pt plus1fil + .\hskip 0pt plus1fil + .\hskip 0pt plus.5fil + }% +} + +% @enddots{} is an end-of-sentence ellipsis. +% +\def\enddots{% + \dots + \spacefactor=\endofsentencespacefactor +} + +% @comma{} is so commas can be inserted into text without messing up +% Texinfo's parsing. +% +\let\comma = , + +% @refill is a no-op. +\let\refill=\relax + +% If working on a large document in chapters, it is convenient to +% be able to disable indexing, cross-referencing, and contents, for test runs. +% This is done with @novalidate (before @setfilename). +% +\newif\iflinks \linkstrue % by default we want the aux files. +\let\novalidate = \linksfalse + +% @setfilename is done at the beginning of every texinfo file. +% So open here the files we need to have open while reading the input. +% This makes it possible to make a .fmt file for texinfo. +\def\setfilename{% + \fixbackslash % Turn off hack to swallow `\input texinfo'. + \iflinks + \tryauxfile + % Open the new aux file. TeX will close it automatically at exit. + \immediate\openout\auxfile=\jobname.aux + \fi % \openindices needs to do some work in any case. + \openindices + \let\setfilename=\comment % Ignore extra @setfilename cmds. + % + % If texinfo.cnf is present on the system, read it. + % Useful for site-wide @afourpaper, etc. + \openin 1 texinfo.cnf + \ifeof 1 \else \input texinfo.cnf \fi + \closein 1 + % + \comment % Ignore the actual filename. +} + +% Called from \setfilename. +% +\def\openindices{% + \newindex{cp}% + \newcodeindex{fn}% + \newcodeindex{vr}% + \newcodeindex{tp}% + \newcodeindex{ky}% + \newcodeindex{pg}% +} + +% @bye. +\outer\def\bye{\pagealignmacro\tracingstats=1\ptexend} + + +\message{pdf,} +% adobe `portable' document format +\newcount\tempnum +\newcount\lnkcount +\newtoks\filename +\newcount\filenamelength +\newcount\pgn +\newtoks\toksA +\newtoks\toksB +\newtoks\toksC +\newtoks\toksD +\newbox\boxA +\newcount\countA +\newif\ifpdf +\newif\ifpdfmakepagedest + +% when pdftex is run in dvi mode, \pdfoutput is defined (so \pdfoutput=1 +% can be set). So we test for \relax and 0 as well as \undefined, +% borrowed from ifpdf.sty. +\ifx\pdfoutput\undefined +\else + \ifx\pdfoutput\relax + \else + \ifcase\pdfoutput + \else + \pdftrue + \fi + \fi +\fi + +% PDF uses PostScript string constants for the names of xref targets, +% for display in the outlines, and in other places. Thus, we have to +% double any backslashes. Otherwise, a name like "\node" will be +% interpreted as a newline (\n), followed by o, d, e. Not good. +% http://www.ntg.nl/pipermail/ntg-pdftex/2004-July/000654.html +% (and related messages, the final outcome is that it is up to the TeX +% user to double the backslashes and otherwise make the string valid, so +% that's what we do). + +% double active backslashes. +% +{\catcode`\@=0 \catcode`\\=\active + @gdef@activebackslashdouble{% + @catcode`@\=@active + @let\=@doublebackslash} +} + +% To handle parens, we must adopt a different approach, since parens are +% not active characters. hyperref.dtx (which has the same problem as +% us) handles it with this amazing macro to replace tokens. I've +% tinkered with it a little for texinfo, but it's definitely from there. +% +% #1 is the tokens to replace. +% #2 is the replacement. +% #3 is the control sequence with the string. +% +\def\HyPsdSubst#1#2#3{% + \def\HyPsdReplace##1#1##2\END{% + ##1% + \ifx\\##2\\% + \else + #2% + \HyReturnAfterFi{% + \HyPsdReplace##2\END + }% + \fi + }% + \xdef#3{\expandafter\HyPsdReplace#3#1\END}% +} +\long\def\HyReturnAfterFi#1\fi{\fi#1} + +% #1 is a control sequence in which to do the replacements. +\def\backslashparens#1{% + \xdef#1{#1}% redefine it as its expansion; the definition is simply + % \lastnode when called from \setref -> \pdfmkdest. + \HyPsdSubst{(}{\realbackslash(}{#1}% + \HyPsdSubst{)}{\realbackslash)}{#1}% +} + +\ifpdf + \input pdfcolor + \pdfcatalog{/PageMode /UseOutlines}% + % #1 is image name, #2 width (might be empty/whitespace), #3 height (ditto). + \def\dopdfimage#1#2#3{% + \def\imagewidth{#2}\setbox0 = \hbox{\ignorespaces #2}% + \def\imageheight{#3}\setbox2 = \hbox{\ignorespaces #3}% + % without \immediate, pdftex seg faults when the same image is + % included twice. (Version 3.14159-pre-1.0-unofficial-20010704.) + \ifnum\pdftexversion < 14 + \immediate\pdfimage + \else + \immediate\pdfximage + \fi + \ifdim \wd0 >0pt width \imagewidth \fi + \ifdim \wd2 >0pt height \imageheight \fi + \ifnum\pdftexversion<13 + #1.pdf% + \else + {#1.pdf}% + \fi + \ifnum\pdftexversion < 14 \else + \pdfrefximage \pdflastximage + \fi} + \def\pdfmkdest#1{{% + % We have to set dummies so commands such as @code, and characters + % such as \, aren't expanded when present in a section title. + \atdummies + \activebackslashdouble + \def\pdfdestname{#1}% + \backslashparens\pdfdestname + \pdfdest name{\pdfdestname} xyz% + }}% + % + % used to mark target names; must be expandable. + \def\pdfmkpgn#1{#1}% + % + \let\linkcolor = \Blue % was Cyan, but that seems light? + \def\endlink{\Black\pdfendlink} + % Adding outlines to PDF; macros for calculating structure of outlines + % come from Petr Olsak + \def\expnumber#1{\expandafter\ifx\csname#1\endcsname\relax 0% + \else \csname#1\endcsname \fi} + \def\advancenumber#1{\tempnum=\expnumber{#1}\relax + \advance\tempnum by 1 + \expandafter\xdef\csname#1\endcsname{\the\tempnum}} + % + % #1 is the section text, which is what will be displayed in the + % outline by the pdf viewer. #2 is the pdf expression for the number + % of subentries (or empty, for subsubsections). #3 is the node text, + % which might be empty if this toc entry had no corresponding node. + % #4 is the page number + % + \def\dopdfoutline#1#2#3#4{% + % Generate a link to the node text if that exists; else, use the + % page number. We could generate a destination for the section + % text in the case where a section has no node, but it doesn't + % seem worth the trouble, since most documents are normally structured. + \def\pdfoutlinedest{#3}% + \ifx\pdfoutlinedest\empty + \def\pdfoutlinedest{#4}% + \else + % Doubled backslashes in the name. + {\activebackslashdouble \xdef\pdfoutlinedest{#3}% + \backslashparens\pdfoutlinedest}% + \fi + % + % Also double the backslashes in the display string. + {\activebackslashdouble \xdef\pdfoutlinetext{#1}% + \backslashparens\pdfoutlinetext}% + % + \pdfoutline goto name{\pdfmkpgn{\pdfoutlinedest}}#2{\pdfoutlinetext}% + } + % + \def\pdfmakeoutlines{% + \begingroup + % Thanh's hack / proper braces in bookmarks + \edef\mylbrace{\iftrue \string{\else}\fi}\let\{=\mylbrace + \edef\myrbrace{\iffalse{\else\string}\fi}\let\}=\myrbrace + % + % Read toc silently, to get counts of subentries for \pdfoutline. + \def\numchapentry##1##2##3##4{% + \def\thischapnum{##2}% + \def\thissecnum{0}% + \def\thissubsecnum{0}% + }% + \def\numsecentry##1##2##3##4{% + \advancenumber{chap\thischapnum}% + \def\thissecnum{##2}% + \def\thissubsecnum{0}% + }% + \def\numsubsecentry##1##2##3##4{% + \advancenumber{sec\thissecnum}% + \def\thissubsecnum{##2}% + }% + \def\numsubsubsecentry##1##2##3##4{% + \advancenumber{subsec\thissubsecnum}% + }% + \def\thischapnum{0}% + \def\thissecnum{0}% + \def\thissubsecnum{0}% + % + % use \def rather than \let here because we redefine \chapentry et + % al. a second time, below. + \def\appentry{\numchapentry}% + \def\appsecentry{\numsecentry}% + \def\appsubsecentry{\numsubsecentry}% + \def\appsubsubsecentry{\numsubsubsecentry}% + \def\unnchapentry{\numchapentry}% + \def\unnsecentry{\numsecentry}% + \def\unnsubsecentry{\numsubsecentry}% + \def\unnsubsubsecentry{\numsubsubsecentry}% + \readdatafile{toc}% + % + % Read toc second time, this time actually producing the outlines. + % The `-' means take the \expnumber as the absolute number of + % subentries, which we calculated on our first read of the .toc above. + % + % We use the node names as the destinations. + \def\numchapentry##1##2##3##4{% + \dopdfoutline{##1}{count-\expnumber{chap##2}}{##3}{##4}}% + \def\numsecentry##1##2##3##4{% + \dopdfoutline{##1}{count-\expnumber{sec##2}}{##3}{##4}}% + \def\numsubsecentry##1##2##3##4{% + \dopdfoutline{##1}{count-\expnumber{subsec##2}}{##3}{##4}}% + \def\numsubsubsecentry##1##2##3##4{% count is always zero + \dopdfoutline{##1}{}{##3}{##4}}% + % + % PDF outlines are displayed using system fonts, instead of + % document fonts. Therefore we cannot use special characters, + % since the encoding is unknown. For example, the eogonek from + % Latin 2 (0xea) gets translated to a | character. Info from + % Staszek Wawrykiewicz, 19 Jan 2004 04:09:24 +0100. + % + % xx to do this right, we have to translate 8-bit characters to + % their "best" equivalent, based on the @documentencoding. Right + % now, I guess we'll just let the pdf reader have its way. + \indexnofonts + \setupdatafile + \catcode`\\=\active \otherbackslash + \input \jobname.toc + \endgroup + } + % + \def\skipspaces#1{\def\PP{#1}\def\D{|}% + \ifx\PP\D\let\nextsp\relax + \else\let\nextsp\skipspaces + \ifx\p\space\else\addtokens{\filename}{\PP}% + \advance\filenamelength by 1 + \fi + \fi + \nextsp} + \def\getfilename#1{\filenamelength=0\expandafter\skipspaces#1|\relax} + \ifnum\pdftexversion < 14 + \let \startlink \pdfannotlink + \else + \let \startlink \pdfstartlink + \fi + % make a live url in pdf output. + \def\pdfurl#1{% + \begingroup + % it seems we really need yet another set of dummies; have not + % tried to figure out what each command should do in the context + % of @url. for now, just make @/ a no-op, that's the only one + % people have actually reported a problem with. + % + \normalturnoffactive + \def\@{@}% + \let\/=\empty + \makevalueexpandable + \leavevmode\Red + \startlink attr{/Border [0 0 0]}% + user{/Subtype /Link /A << /S /URI /URI (#1) >>}% + \endgroup} + \def\pdfgettoks#1.{\setbox\boxA=\hbox{\toksA={#1.}\toksB={}\maketoks}} + \def\addtokens#1#2{\edef\addtoks{\noexpand#1={\the#1#2}}\addtoks} + \def\adn#1{\addtokens{\toksC}{#1}\global\countA=1\let\next=\maketoks} + \def\poptoks#1#2|ENDTOKS|{\let\first=#1\toksD={#1}\toksA={#2}} + \def\maketoks{% + \expandafter\poptoks\the\toksA|ENDTOKS|\relax + \ifx\first0\adn0 + \else\ifx\first1\adn1 \else\ifx\first2\adn2 \else\ifx\first3\adn3 + \else\ifx\first4\adn4 \else\ifx\first5\adn5 \else\ifx\first6\adn6 + \else\ifx\first7\adn7 \else\ifx\first8\adn8 \else\ifx\first9\adn9 + \else + \ifnum0=\countA\else\makelink\fi + \ifx\first.\let\next=\done\else + \let\next=\maketoks + \addtokens{\toksB}{\the\toksD} + \ifx\first,\addtokens{\toksB}{\space}\fi + \fi + \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi + \next} + \def\makelink{\addtokens{\toksB}% + {\noexpand\pdflink{\the\toksC}}\toksC={}\global\countA=0} + \def\pdflink#1{% + \startlink attr{/Border [0 0 0]} goto name{\pdfmkpgn{#1}} + \linkcolor #1\endlink} + \def\done{\edef\st{\global\noexpand\toksA={\the\toksB}}\st} +\else + \let\pdfmkdest = \gobble + \let\pdfurl = \gobble + \let\endlink = \relax + \let\linkcolor = \relax + \let\pdfmakeoutlines = \relax +\fi % \ifx\pdfoutput + + +\message{fonts,} + +% Change the current font style to #1, remembering it in \curfontstyle. +% For now, we do not accumulate font styles: @b{@i{foo}} prints foo in +% italics, not bold italics. +% +\def\setfontstyle#1{% + \def\curfontstyle{#1}% not as a control sequence, because we are \edef'd. + \csname ten#1\endcsname % change the current font +} + +% Select #1 fonts with the current style. +% +\def\selectfonts#1{\csname #1fonts\endcsname \csname\curfontstyle\endcsname} + +\def\rm{\fam=0 \setfontstyle{rm}} +\def\it{\fam=\itfam \setfontstyle{it}} +\def\sl{\fam=\slfam \setfontstyle{sl}} +\def\bf{\fam=\bffam \setfontstyle{bf}}\def\bfstylename{bf} +\def\tt{\fam=\ttfam \setfontstyle{tt}} + +% Texinfo sort of supports the sans serif font style, which plain TeX does not. +% So we set up a \sf. +\newfam\sffam +\def\sf{\fam=\sffam \setfontstyle{sf}} +\let\li = \sf % Sometimes we call it \li, not \sf. + +% We don't need math for this font style. +\def\ttsl{\setfontstyle{ttsl}} + + +% Default leading. +\newdimen\textleading \textleading = 13.2pt + +% Set the baselineskip to #1, and the lineskip and strut size +% correspondingly. There is no deep meaning behind these magic numbers +% used as factors; they just match (closely enough) what Knuth defined. +% +\def\lineskipfactor{.08333} +\def\strutheightpercent{.70833} +\def\strutdepthpercent {.29167} +% +\def\setleading#1{% + \normalbaselineskip = #1\relax + \normallineskip = \lineskipfactor\normalbaselineskip + \normalbaselines + \setbox\strutbox =\hbox{% + \vrule width0pt height\strutheightpercent\baselineskip + depth \strutdepthpercent \baselineskip + }% +} + + +% Set the font macro #1 to the font named #2, adding on the +% specified font prefix (normally `cm'). +% #3 is the font's design size, #4 is a scale factor +\def\setfont#1#2#3#4{\font#1=\fontprefix#2#3 scaled #4} + + +% Use cm as the default font prefix. +% To specify the font prefix, you must define \fontprefix +% before you read in texinfo.tex. +\ifx\fontprefix\undefined +\def\fontprefix{cm} +\fi +% Support font families that don't use the same naming scheme as CM. +\def\rmshape{r} +\def\rmbshape{bx} %where the normal face is bold +\def\bfshape{b} +\def\bxshape{bx} +\def\ttshape{tt} +\def\ttbshape{tt} +\def\ttslshape{sltt} +\def\itshape{ti} +\def\itbshape{bxti} +\def\slshape{sl} +\def\slbshape{bxsl} +\def\sfshape{ss} +\def\sfbshape{ss} +\def\scshape{csc} +\def\scbshape{csc} + +% Definitions for a main text size of 11pt. This is the default in +% Texinfo. +% +\def\definetextfontsizexi{ +% Text fonts (11.2pt, magstep1). +\def\textnominalsize{11pt} +\edef\mainmagstep{\magstephalf} +\setfont\textrm\rmshape{10}{\mainmagstep} +\setfont\texttt\ttshape{10}{\mainmagstep} +\setfont\textbf\bfshape{10}{\mainmagstep} +\setfont\textit\itshape{10}{\mainmagstep} +\setfont\textsl\slshape{10}{\mainmagstep} +\setfont\textsf\sfshape{10}{\mainmagstep} +\setfont\textsc\scshape{10}{\mainmagstep} +\setfont\textttsl\ttslshape{10}{\mainmagstep} +\font\texti=cmmi10 scaled \mainmagstep +\font\textsy=cmsy10 scaled \mainmagstep + +% A few fonts for @defun names and args. +\setfont\defbf\bfshape{10}{\magstep1} +\setfont\deftt\ttshape{10}{\magstep1} +\setfont\defttsl\ttslshape{10}{\magstep1} +\def\df{\let\tentt=\deftt \let\tenbf = \defbf \let\tenttsl=\defttsl \bf} + +% Fonts for indices, footnotes, small examples (9pt). +\def\smallnominalsize{9pt} +\setfont\smallrm\rmshape{9}{1000} +\setfont\smalltt\ttshape{9}{1000} +\setfont\smallbf\bfshape{10}{900} +\setfont\smallit\itshape{9}{1000} +\setfont\smallsl\slshape{9}{1000} +\setfont\smallsf\sfshape{9}{1000} +\setfont\smallsc\scshape{10}{900} +\setfont\smallttsl\ttslshape{10}{900} +\font\smalli=cmmi9 +\font\smallsy=cmsy9 + +% Fonts for small examples (8pt). +\def\smallernominalsize{8pt} +\setfont\smallerrm\rmshape{8}{1000} +\setfont\smallertt\ttshape{8}{1000} +\setfont\smallerbf\bfshape{10}{800} +\setfont\smallerit\itshape{8}{1000} +\setfont\smallersl\slshape{8}{1000} +\setfont\smallersf\sfshape{8}{1000} +\setfont\smallersc\scshape{10}{800} +\setfont\smallerttsl\ttslshape{10}{800} +\font\smalleri=cmmi8 +\font\smallersy=cmsy8 + +% Fonts for title page (20.4pt): +\def\titlenominalsize{20pt} +\setfont\titlerm\rmbshape{12}{\magstep3} +\setfont\titleit\itbshape{10}{\magstep4} +\setfont\titlesl\slbshape{10}{\magstep4} +\setfont\titlett\ttbshape{12}{\magstep3} +\setfont\titlettsl\ttslshape{10}{\magstep4} +\setfont\titlesf\sfbshape{17}{\magstep1} +\let\titlebf=\titlerm +\setfont\titlesc\scbshape{10}{\magstep4} +\font\titlei=cmmi12 scaled \magstep3 +\font\titlesy=cmsy10 scaled \magstep4 +\def\authorrm{\secrm} +\def\authortt{\sectt} + +% Chapter (and unnumbered) fonts (17.28pt). +\def\chapnominalsize{17pt} +\setfont\chaprm\rmbshape{12}{\magstep2} +\setfont\chapit\itbshape{10}{\magstep3} +\setfont\chapsl\slbshape{10}{\magstep3} +\setfont\chaptt\ttbshape{12}{\magstep2} +\setfont\chapttsl\ttslshape{10}{\magstep3} +\setfont\chapsf\sfbshape{17}{1000} +\let\chapbf=\chaprm +\setfont\chapsc\scbshape{10}{\magstep3} +\font\chapi=cmmi12 scaled \magstep2 +\font\chapsy=cmsy10 scaled \magstep3 + +% Section fonts (14.4pt). +\def\secnominalsize{14pt} +\setfont\secrm\rmbshape{12}{\magstep1} +\setfont\secit\itbshape{10}{\magstep2} +\setfont\secsl\slbshape{10}{\magstep2} +\setfont\sectt\ttbshape{12}{\magstep1} +\setfont\secttsl\ttslshape{10}{\magstep2} +\setfont\secsf\sfbshape{12}{\magstep1} +\let\secbf\secrm +\setfont\secsc\scbshape{10}{\magstep2} +\font\seci=cmmi12 scaled \magstep1 +\font\secsy=cmsy10 scaled \magstep2 + +% Subsection fonts (13.15pt). +\def\ssecnominalsize{13pt} +\setfont\ssecrm\rmbshape{12}{\magstephalf} +\setfont\ssecit\itbshape{10}{1315} +\setfont\ssecsl\slbshape{10}{1315} +\setfont\ssectt\ttbshape{12}{\magstephalf} +\setfont\ssecttsl\ttslshape{10}{1315} +\setfont\ssecsf\sfbshape{12}{\magstephalf} +\let\ssecbf\ssecrm +\setfont\ssecsc\scbshape{10}{1315} +\font\sseci=cmmi12 scaled \magstephalf +\font\ssecsy=cmsy10 scaled 1315 + +% Reduced fonts for @acro in text (10pt). +\def\reducednominalsize{10pt} +\setfont\reducedrm\rmshape{10}{1000} +\setfont\reducedtt\ttshape{10}{1000} +\setfont\reducedbf\bfshape{10}{1000} +\setfont\reducedit\itshape{10}{1000} +\setfont\reducedsl\slshape{10}{1000} +\setfont\reducedsf\sfshape{10}{1000} +\setfont\reducedsc\scshape{10}{1000} +\setfont\reducedttsl\ttslshape{10}{1000} +\font\reducedi=cmmi10 +\font\reducedsy=cmsy10 + +% reset the current fonts +\textfonts +\rm +} % end of 11pt text font size definitions + + +% Definitions to make the main text be 10pt Computer Modern, with +% section, chapter, etc., sizes following suit. This is for the GNU +% Press printing of the Emacs 22 manual. Maybe other manuals in the +% future. Used with @smallbook, which sets the leading to 12pt. +% +\def\definetextfontsizex{% +% Text fonts (10pt). +\def\textnominalsize{10pt} +\edef\mainmagstep{1000} +\setfont\textrm\rmshape{10}{\mainmagstep} +\setfont\texttt\ttshape{10}{\mainmagstep} +\setfont\textbf\bfshape{10}{\mainmagstep} +\setfont\textit\itshape{10}{\mainmagstep} +\setfont\textsl\slshape{10}{\mainmagstep} +\setfont\textsf\sfshape{10}{\mainmagstep} +\setfont\textsc\scshape{10}{\mainmagstep} +\setfont\textttsl\ttslshape{10}{\mainmagstep} +\font\texti=cmmi10 scaled \mainmagstep +\font\textsy=cmsy10 scaled \mainmagstep + +% A few fonts for @defun names and args. +\setfont\defbf\bfshape{10}{\magstephalf} +\setfont\deftt\ttshape{10}{\magstephalf} +\setfont\defttsl\ttslshape{10}{\magstephalf} +\def\df{\let\tentt=\deftt \let\tenbf = \defbf \let\tenttsl=\defttsl \bf} + +% Fonts for indices, footnotes, small examples (9pt). +\def\smallnominalsize{9pt} +\setfont\smallrm\rmshape{9}{1000} +\setfont\smalltt\ttshape{9}{1000} +\setfont\smallbf\bfshape{10}{900} +\setfont\smallit\itshape{9}{1000} +\setfont\smallsl\slshape{9}{1000} +\setfont\smallsf\sfshape{9}{1000} +\setfont\smallsc\scshape{10}{900} +\setfont\smallttsl\ttslshape{10}{900} +\font\smalli=cmmi9 +\font\smallsy=cmsy9 + +% Fonts for small examples (8pt). +\def\smallernominalsize{8pt} +\setfont\smallerrm\rmshape{8}{1000} +\setfont\smallertt\ttshape{8}{1000} +\setfont\smallerbf\bfshape{10}{800} +\setfont\smallerit\itshape{8}{1000} +\setfont\smallersl\slshape{8}{1000} +\setfont\smallersf\sfshape{8}{1000} +\setfont\smallersc\scshape{10}{800} +\setfont\smallerttsl\ttslshape{10}{800} +\font\smalleri=cmmi8 +\font\smallersy=cmsy8 + +% Fonts for title page (20.4pt): +\def\titlenominalsize{20pt} +\setfont\titlerm\rmbshape{12}{\magstep3} +\setfont\titleit\itbshape{10}{\magstep4} +\setfont\titlesl\slbshape{10}{\magstep4} +\setfont\titlett\ttbshape{12}{\magstep3} +\setfont\titlettsl\ttslshape{10}{\magstep4} +\setfont\titlesf\sfbshape{17}{\magstep1} +\let\titlebf=\titlerm +\setfont\titlesc\scbshape{10}{\magstep4} +\font\titlei=cmmi12 scaled \magstep3 +\font\titlesy=cmsy10 scaled \magstep4 +\def\authorrm{\secrm} +\def\authortt{\sectt} + +% Chapter fonts (14.4pt). +\def\chapnominalsize{14pt} +\setfont\chaprm\rmbshape{12}{\magstep1} +\setfont\chapit\itbshape{10}{\magstep2} +\setfont\chapsl\slbshape{10}{\magstep2} +\setfont\chaptt\ttbshape{12}{\magstep1} +\setfont\chapttsl\ttslshape{10}{\magstep2} +\setfont\chapsf\sfbshape{12}{\magstep1} +\let\chapbf\chaprm +\setfont\chapsc\scbshape{10}{\magstep2} +\font\chapi=cmmi12 scaled \magstep1 +\font\chapsy=cmsy10 scaled \magstep2 + +% Section fonts (12pt). +\def\secnominalsize{12pt} +\setfont\secrm\rmbshape{12}{1000} +\setfont\secit\itbshape{10}{\magstep1} +\setfont\secsl\slbshape{10}{\magstep1} +\setfont\sectt\ttbshape{12}{1000} +\setfont\secttsl\ttslshape{10}{\magstep1} +\setfont\secsf\sfbshape{12}{1000} +\let\secbf\secrm +\setfont\secsc\scbshape{10}{\magstep1} +\font\seci=cmmi12 +\font\secsy=cmsy10 scaled \magstep1 + +% Subsection fonts (10pt). +\def\ssecnominalsize{10pt} +\setfont\ssecrm\rmbshape{10}{1000} +\setfont\ssecit\itbshape{10}{1000} +\setfont\ssecsl\slbshape{10}{1000} +\setfont\ssectt\ttbshape{10}{1000} +\setfont\ssecttsl\ttslshape{10}{1000} +\setfont\ssecsf\sfbshape{10}{1000} +\let\ssecbf\ssecrm +\setfont\ssecsc\scbshape{10}{1000} +\font\sseci=cmmi10 +\font\ssecsy=cmsy10 + +% Reduced fonts for @acro in text (9pt). +\def\reducednominalsize{9pt} +\setfont\reducedrm\rmshape{9}{1000} +\setfont\reducedtt\ttshape{9}{1000} +\setfont\reducedbf\bfshape{10}{900} +\setfont\reducedit\itshape{9}{1000} +\setfont\reducedsl\slshape{9}{1000} +\setfont\reducedsf\sfshape{9}{1000} +\setfont\reducedsc\scshape{10}{900} +\setfont\reducedttsl\ttslshape{10}{900} +\font\reducedi=cmmi9 +\font\reducedsy=cmsy9 + +% reduce space between paragraphs +\divide\parskip by 2 + +% reset the current fonts +\textfonts +\rm +} % end of 10pt text font size definitions + + +% We provide the user-level command +% @fonttextsize 10 +% (or 11) to redefine the text font size. pt is assumed. +% +\def\xword{10} +\def\xiword{11} +% +\parseargdef\fonttextsize{% + \def\textsizearg{#1}% + \wlog{doing @fonttextsize \textsizearg}% + % + % Set \globaldefs so that documents can use this inside @tex, since + % makeinfo 4.8 does not support it, but we need it nonetheless. + % + \begingroup \globaldefs=1 + \ifx\textsizearg\xword \definetextfontsizex + \else \ifx\textsizearg\xiword \definetextfontsizexi + \else + \errhelp=\EMsimple + \errmessage{@fonttextsize only supports `10' or `11', not `\textsizearg'} + \fi\fi + \endgroup +} + + +% In order for the font changes to affect most math symbols and letters, +% we have to define the \textfont of the standard families. Since +% texinfo doesn't allow for producing subscripts and superscripts except +% in the main text, we don't bother to reset \scriptfont and +% \scriptscriptfont (which would also require loading a lot more fonts). +% +\def\resetmathfonts{% + \textfont0=\tenrm \textfont1=\teni \textfont2=\tensy + \textfont\itfam=\tenit \textfont\slfam=\tensl \textfont\bffam=\tenbf + \textfont\ttfam=\tentt \textfont\sffam=\tensf +} + +% The font-changing commands redefine the meanings of \tenSTYLE, instead +% of just \STYLE. We do this because \STYLE needs to also set the +% current \fam for math mode. Our \STYLE (e.g., \rm) commands hardwire +% \tenSTYLE to set the current font. +% +% Each font-changing command also sets the names \lsize (one size lower) +% and \lllsize (three sizes lower). These relative commands are used in +% the LaTeX logo and acronyms. +% +% This all needs generalizing, badly. +% +\def\textfonts{% + \let\tenrm=\textrm \let\tenit=\textit \let\tensl=\textsl + \let\tenbf=\textbf \let\tentt=\texttt \let\smallcaps=\textsc + \let\tensf=\textsf \let\teni=\texti \let\tensy=\textsy + \let\tenttsl=\textttsl + \def\curfontsize{text}% + \def\lsize{reduced}\def\lllsize{smaller}% + \resetmathfonts \setleading{\textleading}} +\def\titlefonts{% + \let\tenrm=\titlerm \let\tenit=\titleit \let\tensl=\titlesl + \let\tenbf=\titlebf \let\tentt=\titlett \let\smallcaps=\titlesc + \let\tensf=\titlesf \let\teni=\titlei \let\tensy=\titlesy + \let\tenttsl=\titlettsl + \def\curfontsize{title}% + \def\lsize{chap}\def\lllsize{subsec}% + \resetmathfonts \setleading{25pt}} +\def\titlefont#1{{\titlefonts\rm #1}} +\def\chapfonts{% + \let\tenrm=\chaprm \let\tenit=\chapit \let\tensl=\chapsl + \let\tenbf=\chapbf \let\tentt=\chaptt \let\smallcaps=\chapsc + \let\tensf=\chapsf \let\teni=\chapi \let\tensy=\chapsy + \let\tenttsl=\chapttsl + \def\curfontsize{chap}% + \def\lsize{sec}\def\lllsize{text}% + \resetmathfonts \setleading{19pt}} +\def\secfonts{% + \let\tenrm=\secrm \let\tenit=\secit \let\tensl=\secsl + \let\tenbf=\secbf \let\tentt=\sectt \let\smallcaps=\secsc + \let\tensf=\secsf \let\teni=\seci \let\tensy=\secsy + \let\tenttsl=\secttsl + \def\curfontsize{sec}% + \def\lsize{subsec}\def\lllsize{reduced}% + \resetmathfonts \setleading{16pt}} +\def\subsecfonts{% + \let\tenrm=\ssecrm \let\tenit=\ssecit \let\tensl=\ssecsl + \let\tenbf=\ssecbf \let\tentt=\ssectt \let\smallcaps=\ssecsc + \let\tensf=\ssecsf \let\teni=\sseci \let\tensy=\ssecsy + \let\tenttsl=\ssecttsl + \def\curfontsize{ssec}% + \def\lsize{text}\def\lllsize{small}% + \resetmathfonts \setleading{15pt}} +\let\subsubsecfonts = \subsecfonts +\def\reducedfonts{% + \let\tenrm=\reducedrm \let\tenit=\reducedit \let\tensl=\reducedsl + \let\tenbf=\reducedbf \let\tentt=\reducedtt \let\reducedcaps=\reducedsc + \let\tensf=\reducedsf \let\teni=\reducedi \let\tensy=\reducedsy + \let\tenttsl=\reducedttsl + \def\curfontsize{reduced}% + \def\lsize{small}\def\lllsize{smaller}% + \resetmathfonts \setleading{10.5pt}} +\def\smallfonts{% + \let\tenrm=\smallrm \let\tenit=\smallit \let\tensl=\smallsl + \let\tenbf=\smallbf \let\tentt=\smalltt \let\smallcaps=\smallsc + \let\tensf=\smallsf \let\teni=\smalli \let\tensy=\smallsy + \let\tenttsl=\smallttsl + \def\curfontsize{small}% + \def\lsize{smaller}\def\lllsize{smaller}% + \resetmathfonts \setleading{10.5pt}} +\def\smallerfonts{% + \let\tenrm=\smallerrm \let\tenit=\smallerit \let\tensl=\smallersl + \let\tenbf=\smallerbf \let\tentt=\smallertt \let\smallcaps=\smallersc + \let\tensf=\smallersf \let\teni=\smalleri \let\tensy=\smallersy + \let\tenttsl=\smallerttsl + \def\curfontsize{smaller}% + \def\lsize{smaller}\def\lllsize{smaller}% + \resetmathfonts \setleading{9.5pt}} + +% Set the fonts to use with the @small... environments. +\let\smallexamplefonts = \smallfonts + +% About \smallexamplefonts. If we use \smallfonts (9pt), @smallexample +% can fit this many characters: +% 8.5x11=86 smallbook=72 a4=90 a5=69 +% If we use \scriptfonts (8pt), then we can fit this many characters: +% 8.5x11=90+ smallbook=80 a4=90+ a5=77 +% For me, subjectively, the few extra characters that fit aren't worth +% the additional smallness of 8pt. So I'm making the default 9pt. +% +% By the way, for comparison, here's what fits with @example (10pt): +% 8.5x11=71 smallbook=60 a4=75 a5=58 +% +% I wish the USA used A4 paper. +% --karl, 24jan03. + + +% Set up the default fonts, so we can use them for creating boxes. +% +\definetextfontsizexi + +% Define these so they can be easily changed for other fonts. +\def\angleleft{$\langle$} +\def\angleright{$\rangle$} + +% Count depth in font-changes, for error checks +\newcount\fontdepth \fontdepth=0 + +% Fonts for short table of contents. +\setfont\shortcontrm\rmshape{12}{1000} +\setfont\shortcontbf\bfshape{10}{\magstep1} % no cmb12 +\setfont\shortcontsl\slshape{12}{1000} +\setfont\shortconttt\ttshape{12}{1000} + +%% Add scribe-like font environments, plus @l for inline lisp (usually sans +%% serif) and @ii for TeX italic + +% \smartitalic{ARG} outputs arg in italics, followed by an italic correction +% unless the following character is such as not to need one. +\def\smartitalicx{\ifx\next,\else\ifx\next-\else\ifx\next.\else + \ptexslash\fi\fi\fi} +\def\smartslanted#1{{\ifusingtt\ttsl\sl #1}\futurelet\next\smartitalicx} +\def\smartitalic#1{{\ifusingtt\ttsl\it #1}\futurelet\next\smartitalicx} + +% like \smartslanted except unconditionally uses \ttsl. +% @var is set to this for defun arguments. +\def\ttslanted#1{{\ttsl #1}\futurelet\next\smartitalicx} + +% like \smartslanted except unconditionally use \sl. We never want +% ttsl for book titles, do we? +\def\cite#1{{\sl #1}\futurelet\next\smartitalicx} + +\let\i=\smartitalic +\let\slanted=\smartslanted +\let\var=\smartslanted +\let\dfn=\smartslanted +\let\emph=\smartitalic + +% @b, explicit bold. +\def\b#1{{\bf #1}} +\let\strong=\b + +% @sansserif, explicit sans. +\def\sansserif#1{{\sf #1}} + +% We can't just use \exhyphenpenalty, because that only has effect at +% the end of a paragraph. Restore normal hyphenation at the end of the +% group within which \nohyphenation is presumably called. +% +\def\nohyphenation{\hyphenchar\font = -1 \aftergroup\restorehyphenation} +\def\restorehyphenation{\hyphenchar\font = `- } + +% Set sfcode to normal for the chars that usually have another value. +% Can't use plain's \frenchspacing because it uses the `\x notation, and +% sometimes \x has an active definition that messes things up. +% +\catcode`@=11 + \def\plainfrenchspacing{% + \sfcode\dotChar =\@m \sfcode\questChar=\@m \sfcode\exclamChar=\@m + \sfcode\colonChar=\@m \sfcode\semiChar =\@m \sfcode\commaChar =\@m + \def\endofsentencespacefactor{1000}% for @. and friends + } + \def\plainnonfrenchspacing{% + \sfcode`\.3000\sfcode`\?3000\sfcode`\!3000 + \sfcode`\:2000\sfcode`\;1500\sfcode`\,1250 + \def\endofsentencespacefactor{3000}% for @. and friends + } +\catcode`@=\other +\def\endofsentencespacefactor{3000}% default + +\def\t#1{% + {\tt \rawbackslash \plainfrenchspacing #1}% + \null +} +\def\samp#1{`\tclose{#1}'\null} +\setfont\keyrm\rmshape{8}{1000} +\font\keysy=cmsy9 +\def\key#1{{\keyrm\textfont2=\keysy \leavevmode\hbox{% + \raise0.4pt\hbox{\angleleft}\kern-.08em\vtop{% + \vbox{\hrule\kern-0.4pt + \hbox{\raise0.4pt\hbox{\vphantom{\angleleft}}#1}}% + \kern-0.4pt\hrule}% + \kern-.06em\raise0.4pt\hbox{\angleright}}}} +% The old definition, with no lozenge: +%\def\key #1{{\ttsl \nohyphenation \uppercase{#1}}\null} +\def\ctrl #1{{\tt \rawbackslash \hat}#1} + +% @file, @option are the same as @samp. +\let\file=\samp +\let\option=\samp + +% @code is a modification of @t, +% which makes spaces the same size as normal in the surrounding text. +\def\tclose#1{% + {% + % Change normal interword space to be same as for the current font. + \spaceskip = \fontdimen2\font + % + % Switch to typewriter. + \tt + % + % But `\ ' produces the large typewriter interword space. + \def\ {{\spaceskip = 0pt{} }}% + % + % Turn off hyphenation. + \nohyphenation + % + \rawbackslash + \plainfrenchspacing + #1% + }% + \null +} + +% We *must* turn on hyphenation at `-' and `_' in @code. +% Otherwise, it is too hard to avoid overfull hboxes +% in the Emacs manual, the Library manual, etc. + +% Unfortunately, TeX uses one parameter (\hyphenchar) to control +% both hyphenation at - and hyphenation within words. +% We must therefore turn them both off (\tclose does that) +% and arrange explicitly to hyphenate at a dash. +% -- rms. +{ + \catcode`\-=\active \catcode`\_=\active + \catcode`\'=\active \catcode`\`=\active + % + \global\def\code{\begingroup + \catcode\rquoteChar=\active \catcode\lquoteChar=\active + \let'\codequoteright \let`\codequoteleft + % + \catcode\dashChar=\active \catcode\underChar=\active + \ifallowcodebreaks + \let-\codedash + \let_\codeunder + \else + \let-\realdash + \let_\realunder + \fi + \codex + } +} + +\def\realdash{-} +\def\codedash{-\discretionary{}{}{}} +\def\codeunder{% + % this is all so @math{@code{var_name}+1} can work. In math mode, _ + % is "active" (mathcode"8000) and \normalunderscore (or \char95, etc.) + % will therefore expand the active definition of _, which is us + % (inside @code that is), therefore an endless loop. + \ifusingtt{\ifmmode + \mathchar"075F % class 0=ordinary, family 7=ttfam, pos 0x5F=_. + \else\normalunderscore \fi + \discretionary{}{}{}}% + {\_}% +} +\def\codex #1{\tclose{#1}\endgroup} + +% An additional complication: the above will allow breaks after, e.g., +% each of the four underscores in __typeof__. This is undesirable in +% some manuals, especially if they don't have long identifiers in +% general. @allowcodebreaks provides a way to control this. +% +\newif\ifallowcodebreaks \allowcodebreakstrue + +\def\keywordtrue{true} +\def\keywordfalse{false} + +\parseargdef\allowcodebreaks{% + \def\txiarg{#1}% + \ifx\txiarg\keywordtrue + \allowcodebreakstrue + \else\ifx\txiarg\keywordfalse + \allowcodebreaksfalse + \else + \errhelp = \EMsimple + \errmessage{Unknown @allowcodebreaks option `\txiarg'}% + \fi\fi +} + +% @kbd is like @code, except that if the argument is just one @key command, +% then @kbd has no effect. + +% @kbdinputstyle -- arg is `distinct' (@kbd uses slanted tty font always), +% `example' (@kbd uses ttsl only inside of @example and friends), +% or `code' (@kbd uses normal tty font always). +\parseargdef\kbdinputstyle{% + \def\txiarg{#1}% + \ifx\txiarg\worddistinct + \gdef\kbdexamplefont{\ttsl}\gdef\kbdfont{\ttsl}% + \else\ifx\txiarg\wordexample + \gdef\kbdexamplefont{\ttsl}\gdef\kbdfont{\tt}% + \else\ifx\txiarg\wordcode + \gdef\kbdexamplefont{\tt}\gdef\kbdfont{\tt}% + \else + \errhelp = \EMsimple + \errmessage{Unknown @kbdinputstyle option `\txiarg'}% + \fi\fi\fi +} +\def\worddistinct{distinct} +\def\wordexample{example} +\def\wordcode{code} + +% Default is `distinct.' +\kbdinputstyle distinct + +\def\xkey{\key} +\def\kbdfoo#1#2#3\par{\def\one{#1}\def\three{#3}\def\threex{??}% +\ifx\one\xkey\ifx\threex\three \key{#2}% +\else{\tclose{\kbdfont\look}}\fi +\else{\tclose{\kbdfont\look}}\fi} + +% For @indicateurl, @env, @command quotes seem unnecessary, so use \code. +\let\indicateurl=\code +\let\env=\code +\let\command=\code + +% @uref (abbreviation for `urlref') takes an optional (comma-separated) +% second argument specifying the text to display and an optional third +% arg as text to display instead of (rather than in addition to) the url +% itself. First (mandatory) arg is the url. Perhaps eventually put in +% a hypertex \special here. +% +\def\uref#1{\douref #1,,,\finish} +\def\douref#1,#2,#3,#4\finish{\begingroup + \unsepspaces + \pdfurl{#1}% + \setbox0 = \hbox{\ignorespaces #3}% + \ifdim\wd0 > 0pt + \unhbox0 % third arg given, show only that + \else + \setbox0 = \hbox{\ignorespaces #2}% + \ifdim\wd0 > 0pt + \ifpdf + \unhbox0 % PDF: 2nd arg given, show only it + \else + \unhbox0\ (\code{#1})% DVI: 2nd arg given, show both it and url + \fi + \else + \code{#1}% only url given, so show it + \fi + \fi + \endlink +\endgroup} + +% @url synonym for @uref, since that's how everyone uses it. +% +\let\url=\uref + +% rms does not like angle brackets --karl, 17may97. +% So now @email is just like @uref, unless we are pdf. +% +%\def\email#1{\angleleft{\tt #1}\angleright} +\ifpdf + \def\email#1{\doemail#1,,\finish} + \def\doemail#1,#2,#3\finish{\begingroup + \unsepspaces + \pdfurl{mailto:#1}% + \setbox0 = \hbox{\ignorespaces #2}% + \ifdim\wd0>0pt\unhbox0\else\code{#1}\fi + \endlink + \endgroup} +\else + \let\email=\uref +\fi + +% Check if we are currently using a typewriter font. Since all the +% Computer Modern typewriter fonts have zero interword stretch (and +% shrink), and it is reasonable to expect all typewriter fonts to have +% this property, we can check that font parameter. +% +\def\ifmonospace{\ifdim\fontdimen3\font=0pt } + +% Typeset a dimension, e.g., `in' or `pt'. The only reason for the +% argument is to make the input look right: @dmn{pt} instead of @dmn{}pt. +% +\def\dmn#1{\thinspace #1} + +\def\kbd#1{\def\look{#1}\expandafter\kbdfoo\look??\par} + +% @l was never documented to mean ``switch to the Lisp font'', +% and it is not used as such in any manual I can find. We need it for +% Polish suppressed-l. --karl, 22sep96. +%\def\l#1{{\li #1}\null} + +% Explicit font changes: @r, @sc, undocumented @ii. +\def\r#1{{\rm #1}} % roman font +\def\sc#1{{\smallcaps#1}} % smallcaps font +\def\ii#1{{\it #1}} % italic font + +% @acronym for "FBI", "NATO", and the like. +% We print this one point size smaller, since it's intended for +% all-uppercase. +% +\def\acronym#1{\doacronym #1,,\finish} +\def\doacronym#1,#2,#3\finish{% + {\selectfonts\lsize #1}% + \def\temp{#2}% + \ifx\temp\empty \else + \space ({\unsepspaces \ignorespaces \temp \unskip})% + \fi +} + +% @abbr for "Comput. J." and the like. +% No font change, but don't do end-of-sentence spacing. +% +\def\abbr#1{\doabbr #1,,\finish} +\def\doabbr#1,#2,#3\finish{% + {\plainfrenchspacing #1}% + \def\temp{#2}% + \ifx\temp\empty \else + \space ({\unsepspaces \ignorespaces \temp \unskip})% + \fi +} + +% @pounds{} is a sterling sign, which Knuth put in the CM italic font. +% +\def\pounds{{\it\$}} + +% @euro{} comes from a separate font, depending on the current style. +% We use the free feym* fonts from the eurosym package by Henrik +% Theiling, which support regular, slanted, bold and bold slanted (and +% "outlined" (blackboard board, sort of) versions, which we don't need). +% It is available from http://www.ctan.org/tex-archive/fonts/eurosym. +% +% Although only regular is the truly official Euro symbol, we ignore +% that. The Euro is designed to be slightly taller than the regular +% font height. +% +% feymr - regular +% feymo - slanted +% feybr - bold +% feybo - bold slanted +% +% There is no good (free) typewriter version, to my knowledge. +% A feymr10 euro is ~7.3pt wide, while a normal cmtt10 char is ~5.25pt wide. +% Hmm. +% +% Also doesn't work in math. Do we need to do math with euro symbols? +% Hope not. +% +% +\def\euro{{\eurofont e}} +\def\eurofont{% + % We set the font at each command, rather than predefining it in + % \textfonts and the other font-switching commands, so that + % installations which never need the symbol don't have to have the + % font installed. + % + % There is only one designed size (nominal 10pt), so we always scale + % that to the current nominal size. + % + % By the way, simply using "at 1em" works for cmr10 and the like, but + % does not work for cmbx10 and other extended/shrunken fonts. + % + \def\eurosize{\csname\curfontsize nominalsize\endcsname}% + % + \ifx\curfontstyle\bfstylename + % bold: + \font\thiseurofont = \ifusingit{feybo10}{feybr10} at \eurosize + \else + % regular: + \font\thiseurofont = \ifusingit{feymo10}{feymr10} at \eurosize + \fi + \thiseurofont +} + +% @registeredsymbol - R in a circle. The font for the R should really +% be smaller yet, but lllsize is the best we can do for now. +% Adapted from the plain.tex definition of \copyright. +% +\def\registeredsymbol{% + $^{{\ooalign{\hfil\raise.07ex\hbox{\selectfonts\lllsize R}% + \hfil\crcr\Orb}}% + }$% +} + +% @textdegree - the normal degrees sign. +% +\def\textdegree{$^\circ$} + +% Laurent Siebenmann reports \Orb undefined with: +% Textures 1.7.7 (preloaded format=plain 93.10.14) (68K) 16 APR 2004 02:38 +% so we'll define it if necessary. +% +\ifx\Orb\undefined +\def\Orb{\mathhexbox20D} +\fi + + +\message{page headings,} + +\newskip\titlepagetopglue \titlepagetopglue = 1.5in +\newskip\titlepagebottomglue \titlepagebottomglue = 2pc + +% First the title page. Must do @settitle before @titlepage. +\newif\ifseenauthor +\newif\iffinishedtitlepage + +% Do an implicit @contents or @shortcontents after @end titlepage if the +% user says @setcontentsaftertitlepage or @setshortcontentsaftertitlepage. +% +\newif\ifsetcontentsaftertitlepage + \let\setcontentsaftertitlepage = \setcontentsaftertitlepagetrue +\newif\ifsetshortcontentsaftertitlepage + \let\setshortcontentsaftertitlepage = \setshortcontentsaftertitlepagetrue + +\parseargdef\shorttitlepage{\begingroup\hbox{}\vskip 1.5in \chaprm \centerline{#1}% + \endgroup\page\hbox{}\page} + +\envdef\titlepage{% + % Open one extra group, as we want to close it in the middle of \Etitlepage. + \begingroup + \parindent=0pt \textfonts + % Leave some space at the very top of the page. + \vglue\titlepagetopglue + % No rule at page bottom unless we print one at the top with @title. + \finishedtitlepagetrue + % + % Most title ``pages'' are actually two pages long, with space + % at the top of the second. We don't want the ragged left on the second. + \let\oldpage = \page + \def\page{% + \iffinishedtitlepage\else + \finishtitlepage + \fi + \let\page = \oldpage + \page + \null + }% +} + +\def\Etitlepage{% + \iffinishedtitlepage\else + \finishtitlepage + \fi + % It is important to do the page break before ending the group, + % because the headline and footline are only empty inside the group. + % If we use the new definition of \page, we always get a blank page + % after the title page, which we certainly don't want. + \oldpage + \endgroup + % + % Need this before the \...aftertitlepage checks so that if they are + % in effect the toc pages will come out with page numbers. + \HEADINGSon + % + % If they want short, they certainly want long too. + \ifsetshortcontentsaftertitlepage + \shortcontents + \contents + \global\let\shortcontents = \relax + \global\let\contents = \relax + \fi + % + \ifsetcontentsaftertitlepage + \contents + \global\let\contents = \relax + \global\let\shortcontents = \relax + \fi +} + +\def\finishtitlepage{% + \vskip4pt \hrule height 2pt width \hsize + \vskip\titlepagebottomglue + \finishedtitlepagetrue +} + +%%% Macros to be used within @titlepage: + +\let\subtitlerm=\tenrm +\def\subtitlefont{\subtitlerm \normalbaselineskip = 13pt \normalbaselines} + +\def\authorfont{\authorrm \normalbaselineskip = 16pt \normalbaselines + \let\tt=\authortt} + +\parseargdef\title{% + \checkenv\titlepage + \leftline{\titlefonts\rm #1} + % print a rule at the page bottom also. + \finishedtitlepagefalse + \vskip4pt \hrule height 4pt width \hsize \vskip4pt +} + +\parseargdef\subtitle{% + \checkenv\titlepage + {\subtitlefont \rightline{#1}}% +} + +% @author should come last, but may come many times. +% It can also be used inside @quotation. +% +\parseargdef\author{% + \def\temp{\quotation}% + \ifx\thisenv\temp + \def\quotationauthor{#1}% printed in \Equotation. + \else + \checkenv\titlepage + \ifseenauthor\else \vskip 0pt plus 1filll \seenauthortrue \fi + {\authorfont \leftline{#1}}% + \fi +} + + +%%% Set up page headings and footings. + +\let\thispage=\folio + +\newtoks\evenheadline % headline on even pages +\newtoks\oddheadline % headline on odd pages +\newtoks\evenfootline % footline on even pages +\newtoks\oddfootline % footline on odd pages + +% Now make TeX use those variables +\headline={{\textfonts\rm \ifodd\pageno \the\oddheadline + \else \the\evenheadline \fi}} +\footline={{\textfonts\rm \ifodd\pageno \the\oddfootline + \else \the\evenfootline \fi}\HEADINGShook} +\let\HEADINGShook=\relax + +% Commands to set those variables. +% For example, this is what @headings on does +% @evenheading @thistitle|@thispage|@thischapter +% @oddheading @thischapter|@thispage|@thistitle +% @evenfooting @thisfile|| +% @oddfooting ||@thisfile + + +\def\evenheading{\parsearg\evenheadingxxx} +\def\evenheadingxxx #1{\evenheadingyyy #1\|\|\|\|\finish} +\def\evenheadingyyy #1\|#2\|#3\|#4\finish{% +\global\evenheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} + +\def\oddheading{\parsearg\oddheadingxxx} +\def\oddheadingxxx #1{\oddheadingyyy #1\|\|\|\|\finish} +\def\oddheadingyyy #1\|#2\|#3\|#4\finish{% +\global\oddheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} + +\parseargdef\everyheading{\oddheadingxxx{#1}\evenheadingxxx{#1}}% + +\def\evenfooting{\parsearg\evenfootingxxx} +\def\evenfootingxxx #1{\evenfootingyyy #1\|\|\|\|\finish} +\def\evenfootingyyy #1\|#2\|#3\|#4\finish{% +\global\evenfootline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} + +\def\oddfooting{\parsearg\oddfootingxxx} +\def\oddfootingxxx #1{\oddfootingyyy #1\|\|\|\|\finish} +\def\oddfootingyyy #1\|#2\|#3\|#4\finish{% + \global\oddfootline = {\rlap{\centerline{#2}}\line{#1\hfil#3}}% + % + % Leave some space for the footline. Hopefully ok to assume + % @evenfooting will not be used by itself. + \global\advance\pageheight by -12pt + \global\advance\vsize by -12pt +} + +\parseargdef\everyfooting{\oddfootingxxx{#1}\evenfootingxxx{#1}} + + +% @headings double turns headings on for double-sided printing. +% @headings single turns headings on for single-sided printing. +% @headings off turns them off. +% @headings on same as @headings double, retained for compatibility. +% @headings after turns on double-sided headings after this page. +% @headings doubleafter turns on double-sided headings after this page. +% @headings singleafter turns on single-sided headings after this page. +% By default, they are off at the start of a document, +% and turned `on' after @end titlepage. + +\def\headings #1 {\csname HEADINGS#1\endcsname} + +\def\HEADINGSoff{% +\global\evenheadline={\hfil} \global\evenfootline={\hfil} +\global\oddheadline={\hfil} \global\oddfootline={\hfil}} +\HEADINGSoff +% When we turn headings on, set the page number to 1. +% For double-sided printing, put current file name in lower left corner, +% chapter name on inside top of right hand pages, document +% title on inside top of left hand pages, and page numbers on outside top +% edge of all pages. +\def\HEADINGSdouble{% +\global\pageno=1 +\global\evenfootline={\hfil} +\global\oddfootline={\hfil} +\global\evenheadline={\line{\folio\hfil\thistitle}} +\global\oddheadline={\line{\thischapter\hfil\folio}} +\global\let\contentsalignmacro = \chapoddpage +} +\let\contentsalignmacro = \chappager + +% For single-sided printing, chapter title goes across top left of page, +% page number on top right. +\def\HEADINGSsingle{% +\global\pageno=1 +\global\evenfootline={\hfil} +\global\oddfootline={\hfil} +\global\evenheadline={\line{\thischapter\hfil\folio}} +\global\oddheadline={\line{\thischapter\hfil\folio}} +\global\let\contentsalignmacro = \chappager +} +\def\HEADINGSon{\HEADINGSdouble} + +\def\HEADINGSafter{\let\HEADINGShook=\HEADINGSdoublex} +\let\HEADINGSdoubleafter=\HEADINGSafter +\def\HEADINGSdoublex{% +\global\evenfootline={\hfil} +\global\oddfootline={\hfil} +\global\evenheadline={\line{\folio\hfil\thistitle}} +\global\oddheadline={\line{\thischapter\hfil\folio}} +\global\let\contentsalignmacro = \chapoddpage +} + +\def\HEADINGSsingleafter{\let\HEADINGShook=\HEADINGSsinglex} +\def\HEADINGSsinglex{% +\global\evenfootline={\hfil} +\global\oddfootline={\hfil} +\global\evenheadline={\line{\thischapter\hfil\folio}} +\global\oddheadline={\line{\thischapter\hfil\folio}} +\global\let\contentsalignmacro = \chappager +} + +% Subroutines used in generating headings +% This produces Day Month Year style of output. +% Only define if not already defined, in case a txi-??.tex file has set +% up a different format (e.g., txi-cs.tex does this). +\ifx\today\undefined +\def\today{% + \number\day\space + \ifcase\month + \or\putwordMJan\or\putwordMFeb\or\putwordMMar\or\putwordMApr + \or\putwordMMay\or\putwordMJun\or\putwordMJul\or\putwordMAug + \or\putwordMSep\or\putwordMOct\or\putwordMNov\or\putwordMDec + \fi + \space\number\year} +\fi + +% @settitle line... specifies the title of the document, for headings. +% It generates no output of its own. +\def\thistitle{\putwordNoTitle} +\def\settitle{\parsearg{\gdef\thistitle}} + + +\message{tables,} +% Tables -- @table, @ftable, @vtable, @item(x). + +% default indentation of table text +\newdimen\tableindent \tableindent=.8in +% default indentation of @itemize and @enumerate text +\newdimen\itemindent \itemindent=.3in +% margin between end of table item and start of table text. +\newdimen\itemmargin \itemmargin=.1in + +% used internally for \itemindent minus \itemmargin +\newdimen\itemmax + +% Note @table, @ftable, and @vtable define @item, @itemx, etc., with +% these defs. +% They also define \itemindex +% to index the item name in whatever manner is desired (perhaps none). + +\newif\ifitemxneedsnegativevskip + +\def\itemxpar{\par\ifitemxneedsnegativevskip\nobreak\vskip-\parskip\nobreak\fi} + +\def\internalBitem{\smallbreak \parsearg\itemzzz} +\def\internalBitemx{\itemxpar \parsearg\itemzzz} + +\def\itemzzz #1{\begingroup % + \advance\hsize by -\rightskip + \advance\hsize by -\tableindent + \setbox0=\hbox{\itemindicate{#1}}% + \itemindex{#1}% + \nobreak % This prevents a break before @itemx. + % + % If the item text does not fit in the space we have, put it on a line + % by itself, and do not allow a page break either before or after that + % line. We do not start a paragraph here because then if the next + % command is, e.g., @kindex, the whatsit would get put into the + % horizontal list on a line by itself, resulting in extra blank space. + \ifdim \wd0>\itemmax + % + % Make this a paragraph so we get the \parskip glue and wrapping, + % but leave it ragged-right. + \begingroup + \advance\leftskip by-\tableindent + \advance\hsize by\tableindent + \advance\rightskip by0pt plus1fil + \leavevmode\unhbox0\par + \endgroup + % + % We're going to be starting a paragraph, but we don't want the + % \parskip glue -- logically it's part of the @item we just started. + \nobreak \vskip-\parskip + % + % Stop a page break at the \parskip glue coming up. However, if + % what follows is an environment such as @example, there will be no + % \parskip glue; then the negative vskip we just inserted would + % cause the example and the item to crash together. So we use this + % bizarre value of 10001 as a signal to \aboveenvbreak to insert + % \parskip glue after all. Section titles are handled this way also. + % + \penalty 10001 + \endgroup + \itemxneedsnegativevskipfalse + \else + % The item text fits into the space. Start a paragraph, so that the + % following text (if any) will end up on the same line. + \noindent + % Do this with kerns and \unhbox so that if there is a footnote in + % the item text, it can migrate to the main vertical list and + % eventually be printed. + \nobreak\kern-\tableindent + \dimen0 = \itemmax \advance\dimen0 by \itemmargin \advance\dimen0 by -\wd0 + \unhbox0 + \nobreak\kern\dimen0 + \endgroup + \itemxneedsnegativevskiptrue + \fi +} + +\def\item{\errmessage{@item while not in a list environment}} +\def\itemx{\errmessage{@itemx while not in a list environment}} + +% @table, @ftable, @vtable. +\envdef\table{% + \let\itemindex\gobble + \tablecheck{table}% +} +\envdef\ftable{% + \def\itemindex ##1{\doind {fn}{\code{##1}}}% + \tablecheck{ftable}% +} +\envdef\vtable{% + \def\itemindex ##1{\doind {vr}{\code{##1}}}% + \tablecheck{vtable}% +} +\def\tablecheck#1{% + \ifnum \the\catcode`\^^M=\active + \endgroup + \errmessage{This command won't work in this context; perhaps the problem is + that we are \inenvironment\thisenv}% + \def\next{\doignore{#1}}% + \else + \let\next\tablex + \fi + \next +} +\def\tablex#1{% + \def\itemindicate{#1}% + \parsearg\tabley +} +\def\tabley#1{% + {% + \makevalueexpandable + \edef\temp{\noexpand\tablez #1\space\space\space}% + \expandafter + }\temp \endtablez +} +\def\tablez #1 #2 #3 #4\endtablez{% + \aboveenvbreak + \ifnum 0#1>0 \advance \leftskip by #1\mil \fi + \ifnum 0#2>0 \tableindent=#2\mil \fi + \ifnum 0#3>0 \advance \rightskip by #3\mil \fi + \itemmax=\tableindent + \advance \itemmax by -\itemmargin + \advance \leftskip by \tableindent + \exdentamount=\tableindent + \parindent = 0pt + \parskip = \smallskipamount + \ifdim \parskip=0pt \parskip=2pt \fi + \let\item = \internalBitem + \let\itemx = \internalBitemx +} +\def\Etable{\endgraf\afterenvbreak} +\let\Eftable\Etable +\let\Evtable\Etable +\let\Eitemize\Etable +\let\Eenumerate\Etable + +% This is the counter used by @enumerate, which is really @itemize + +\newcount \itemno + +\envdef\itemize{\parsearg\doitemize} + +\def\doitemize#1{% + \aboveenvbreak + \itemmax=\itemindent + \advance\itemmax by -\itemmargin + \advance\leftskip by \itemindent + \exdentamount=\itemindent + \parindent=0pt + \parskip=\smallskipamount + \ifdim\parskip=0pt \parskip=2pt \fi + \def\itemcontents{#1}% + % @itemize with no arg is equivalent to @itemize @bullet. + \ifx\itemcontents\empty\def\itemcontents{\bullet}\fi + \let\item=\itemizeitem +} + +% Definition of @item while inside @itemize and @enumerate. +% +\def\itemizeitem{% + \advance\itemno by 1 % for enumerations + {\let\par=\endgraf \smallbreak}% reasonable place to break + {% + % If the document has an @itemize directly after a section title, a + % \nobreak will be last on the list, and \sectionheading will have + % done a \vskip-\parskip. In that case, we don't want to zero + % parskip, or the item text will crash with the heading. On the + % other hand, when there is normal text preceding the item (as there + % usually is), we do want to zero parskip, or there would be too much + % space. In that case, we won't have a \nobreak before. At least + % that's the theory. + \ifnum\lastpenalty<10000 \parskip=0in \fi + \noindent + \hbox to 0pt{\hss \itemcontents \kern\itemmargin}% + \vadjust{\penalty 1200}}% not good to break after first line of item. + \flushcr +} + +% \splitoff TOKENS\endmark defines \first to be the first token in +% TOKENS, and \rest to be the remainder. +% +\def\splitoff#1#2\endmark{\def\first{#1}\def\rest{#2}}% + +% Allow an optional argument of an uppercase letter, lowercase letter, +% or number, to specify the first label in the enumerated list. No +% argument is the same as `1'. +% +\envparseargdef\enumerate{\enumeratey #1 \endenumeratey} +\def\enumeratey #1 #2\endenumeratey{% + % If we were given no argument, pretend we were given `1'. + \def\thearg{#1}% + \ifx\thearg\empty \def\thearg{1}\fi + % + % Detect if the argument is a single token. If so, it might be a + % letter. Otherwise, the only valid thing it can be is a number. + % (We will always have one token, because of the test we just made. + % This is a good thing, since \splitoff doesn't work given nothing at + % all -- the first parameter is undelimited.) + \expandafter\splitoff\thearg\endmark + \ifx\rest\empty + % Only one token in the argument. It could still be anything. + % A ``lowercase letter'' is one whose \lccode is nonzero. + % An ``uppercase letter'' is one whose \lccode is both nonzero, and + % not equal to itself. + % Otherwise, we assume it's a number. + % + % We need the \relax at the end of the \ifnum lines to stop TeX from + % continuing to look for a . + % + \ifnum\lccode\expandafter`\thearg=0\relax + \numericenumerate % a number (we hope) + \else + % It's a letter. + \ifnum\lccode\expandafter`\thearg=\expandafter`\thearg\relax + \lowercaseenumerate % lowercase letter + \else + \uppercaseenumerate % uppercase letter + \fi + \fi + \else + % Multiple tokens in the argument. We hope it's a number. + \numericenumerate + \fi +} + +% An @enumerate whose labels are integers. The starting integer is +% given in \thearg. +% +\def\numericenumerate{% + \itemno = \thearg + \startenumeration{\the\itemno}% +} + +% The starting (lowercase) letter is in \thearg. +\def\lowercaseenumerate{% + \itemno = \expandafter`\thearg + \startenumeration{% + % Be sure we're not beyond the end of the alphabet. + \ifnum\itemno=0 + \errmessage{No more lowercase letters in @enumerate; get a bigger + alphabet}% + \fi + \char\lccode\itemno + }% +} + +% The starting (uppercase) letter is in \thearg. +\def\uppercaseenumerate{% + \itemno = \expandafter`\thearg + \startenumeration{% + % Be sure we're not beyond the end of the alphabet. + \ifnum\itemno=0 + \errmessage{No more uppercase letters in @enumerate; get a bigger + alphabet} + \fi + \char\uccode\itemno + }% +} + +% Call \doitemize, adding a period to the first argument and supplying the +% common last two arguments. Also subtract one from the initial value in +% \itemno, since @item increments \itemno. +% +\def\startenumeration#1{% + \advance\itemno by -1 + \doitemize{#1.}\flushcr +} + +% @alphaenumerate and @capsenumerate are abbreviations for giving an arg +% to @enumerate. +% +\def\alphaenumerate{\enumerate{a}} +\def\capsenumerate{\enumerate{A}} +\def\Ealphaenumerate{\Eenumerate} +\def\Ecapsenumerate{\Eenumerate} + + +% @multitable macros +% Amy Hendrickson, 8/18/94, 3/6/96 +% +% @multitable ... @end multitable will make as many columns as desired. +% Contents of each column will wrap at width given in preamble. Width +% can be specified either with sample text given in a template line, +% or in percent of \hsize, the current width of text on page. + +% Table can continue over pages but will only break between lines. + +% To make preamble: +% +% Either define widths of columns in terms of percent of \hsize: +% @multitable @columnfractions .25 .3 .45 +% @item ... +% +% Numbers following @columnfractions are the percent of the total +% current hsize to be used for each column. You may use as many +% columns as desired. + + +% Or use a template: +% @multitable {Column 1 template} {Column 2 template} {Column 3 template} +% @item ... +% using the widest term desired in each column. + +% Each new table line starts with @item, each subsequent new column +% starts with @tab. Empty columns may be produced by supplying @tab's +% with nothing between them for as many times as empty columns are needed, +% ie, @tab@tab@tab will produce two empty columns. + +% @item, @tab do not need to be on their own lines, but it will not hurt +% if they are. + +% Sample multitable: + +% @multitable {Column 1 template} {Column 2 template} {Column 3 template} +% @item first col stuff @tab second col stuff @tab third col +% @item +% first col stuff +% @tab +% second col stuff +% @tab +% third col +% @item first col stuff @tab second col stuff +% @tab Many paragraphs of text may be used in any column. +% +% They will wrap at the width determined by the template. +% @item@tab@tab This will be in third column. +% @end multitable + +% Default dimensions may be reset by user. +% @multitableparskip is vertical space between paragraphs in table. +% @multitableparindent is paragraph indent in table. +% @multitablecolmargin is horizontal space to be left between columns. +% @multitablelinespace is space to leave between table items, baseline +% to baseline. +% 0pt means it depends on current normal line spacing. +% +\newskip\multitableparskip +\newskip\multitableparindent +\newdimen\multitablecolspace +\newskip\multitablelinespace +\multitableparskip=0pt +\multitableparindent=6pt +\multitablecolspace=12pt +\multitablelinespace=0pt + +% Macros used to set up halign preamble: +% +\let\endsetuptable\relax +\def\xendsetuptable{\endsetuptable} +\let\columnfractions\relax +\def\xcolumnfractions{\columnfractions} +\newif\ifsetpercent + +% #1 is the @columnfraction, usually a decimal number like .5, but might +% be just 1. We just use it, whatever it is. +% +\def\pickupwholefraction#1 {% + \global\advance\colcount by 1 + \expandafter\xdef\csname col\the\colcount\endcsname{#1\hsize}% + \setuptable +} + +\newcount\colcount +\def\setuptable#1{% + \def\firstarg{#1}% + \ifx\firstarg\xendsetuptable + \let\go = \relax + \else + \ifx\firstarg\xcolumnfractions + \global\setpercenttrue + \else + \ifsetpercent + \let\go\pickupwholefraction + \else + \global\advance\colcount by 1 + \setbox0=\hbox{#1\unskip\space}% Add a normal word space as a + % separator; typically that is always in the input, anyway. + \expandafter\xdef\csname col\the\colcount\endcsname{\the\wd0}% + \fi + \fi + \ifx\go\pickupwholefraction + % Put the argument back for the \pickupwholefraction call, so + % we'll always have a period there to be parsed. + \def\go{\pickupwholefraction#1}% + \else + \let\go = \setuptable + \fi% + \fi + \go +} + +% multitable-only commands. +% +% @headitem starts a heading row, which we typeset in bold. +% Assignments have to be global since we are inside the implicit group +% of an alignment entry. Note that \everycr resets \everytab. +\def\headitem{\checkenv\multitable \crcr \global\everytab={\bf}\the\everytab}% +% +% A \tab used to include \hskip1sp. But then the space in a template +% line is not enough. That is bad. So let's go back to just `&' until +% we encounter the problem it was intended to solve again. +% --karl, nathan@acm.org, 20apr99. +\def\tab{\checkenv\multitable &\the\everytab}% + +% @multitable ... @end multitable definitions: +% +\newtoks\everytab % insert after every tab. +% +\envdef\multitable{% + \vskip\parskip + \startsavinginserts + % + % @item within a multitable starts a normal row. + % We use \def instead of \let so that if one of the multitable entries + % contains an @itemize, we don't choke on the \item (seen as \crcr aka + % \endtemplate) expanding \doitemize. + \def\item{\crcr}% + % + \tolerance=9500 + \hbadness=9500 + \setmultitablespacing + \parskip=\multitableparskip + \parindent=\multitableparindent + \overfullrule=0pt + \global\colcount=0 + % + \everycr = {% + \noalign{% + \global\everytab={}% + \global\colcount=0 % Reset the column counter. + % Check for saved footnotes, etc. + \checkinserts + % Keeps underfull box messages off when table breaks over pages. + %\filbreak + % Maybe so, but it also creates really weird page breaks when the + % table breaks over pages. Wouldn't \vfil be better? Wait until the + % problem manifests itself, so it can be fixed for real --karl. + }% + }% + % + \parsearg\domultitable +} +\def\domultitable#1{% + % To parse everything between @multitable and @item: + \setuptable#1 \endsetuptable + % + % This preamble sets up a generic column definition, which will + % be used as many times as user calls for columns. + % \vtop will set a single line and will also let text wrap and + % continue for many paragraphs if desired. + \halign\bgroup &% + \global\advance\colcount by 1 + \multistrut + \vtop{% + % Use the current \colcount to find the correct column width: + \hsize=\expandafter\csname col\the\colcount\endcsname + % + % In order to keep entries from bumping into each other + % we will add a \leftskip of \multitablecolspace to all columns after + % the first one. + % + % If a template has been used, we will add \multitablecolspace + % to the width of each template entry. + % + % If the user has set preamble in terms of percent of \hsize we will + % use that dimension as the width of the column, and the \leftskip + % will keep entries from bumping into each other. Table will start at + % left margin and final column will justify at right margin. + % + % Make sure we don't inherit \rightskip from the outer environment. + \rightskip=0pt + \ifnum\colcount=1 + % The first column will be indented with the surrounding text. + \advance\hsize by\leftskip + \else + \ifsetpercent \else + % If user has not set preamble in terms of percent of \hsize + % we will advance \hsize by \multitablecolspace. + \advance\hsize by \multitablecolspace + \fi + % In either case we will make \leftskip=\multitablecolspace: + \leftskip=\multitablecolspace + \fi + % Ignoring space at the beginning and end avoids an occasional spurious + % blank line, when TeX decides to break the line at the space before the + % box from the multistrut, so the strut ends up on a line by itself. + % For example: + % @multitable @columnfractions .11 .89 + % @item @code{#} + % @tab Legal holiday which is valid in major parts of the whole country. + % Is automatically provided with highlighting sequences respectively + % marking characters. + \noindent\ignorespaces##\unskip\multistrut + }\cr +} +\def\Emultitable{% + \crcr + \egroup % end the \halign + \global\setpercentfalse +} + +\def\setmultitablespacing{% + \def\multistrut{\strut}% just use the standard line spacing + % + % Compute \multitablelinespace (if not defined by user) for use in + % \multitableparskip calculation. We used define \multistrut based on + % this, but (ironically) that caused the spacing to be off. + % See bug-texinfo report from Werner Lemberg, 31 Oct 2004 12:52:20 +0100. +\ifdim\multitablelinespace=0pt +\setbox0=\vbox{X}\global\multitablelinespace=\the\baselineskip +\global\advance\multitablelinespace by-\ht0 +\fi +%% Test to see if parskip is larger than space between lines of +%% table. If not, do nothing. +%% If so, set to same dimension as multitablelinespace. +\ifdim\multitableparskip>\multitablelinespace +\global\multitableparskip=\multitablelinespace +\global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller + %% than skip between lines in the table. +\fi% +\ifdim\multitableparskip=0pt +\global\multitableparskip=\multitablelinespace +\global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller + %% than skip between lines in the table. +\fi} + + +\message{conditionals,} + +% @iftex, @ifnotdocbook, @ifnothtml, @ifnotinfo, @ifnotplaintext, +% @ifnotxml always succeed. They currently do nothing; we don't +% attempt to check whether the conditionals are properly nested. But we +% have to remember that they are conditionals, so that @end doesn't +% attempt to close an environment group. +% +\def\makecond#1{% + \expandafter\let\csname #1\endcsname = \relax + \expandafter\let\csname iscond.#1\endcsname = 1 +} +\makecond{iftex} +\makecond{ifnotdocbook} +\makecond{ifnothtml} +\makecond{ifnotinfo} +\makecond{ifnotplaintext} +\makecond{ifnotxml} + +% Ignore @ignore, @ifhtml, @ifinfo, and the like. +% +\def\direntry{\doignore{direntry}} +\def\documentdescription{\doignore{documentdescription}} +\def\docbook{\doignore{docbook}} +\def\html{\doignore{html}} +\def\ifdocbook{\doignore{ifdocbook}} +\def\ifhtml{\doignore{ifhtml}} +\def\ifinfo{\doignore{ifinfo}} +\def\ifnottex{\doignore{ifnottex}} +\def\ifplaintext{\doignore{ifplaintext}} +\def\ifxml{\doignore{ifxml}} +\def\ignore{\doignore{ignore}} +\def\menu{\doignore{menu}} +\def\xml{\doignore{xml}} + +% Ignore text until a line `@end #1', keeping track of nested conditionals. +% +% A count to remember the depth of nesting. +\newcount\doignorecount + +\def\doignore#1{\begingroup + % Scan in ``verbatim'' mode: + \obeylines + \catcode`\@ = \other + \catcode`\{ = \other + \catcode`\} = \other + % + % Make sure that spaces turn into tokens that match what \doignoretext wants. + \spaceisspace + % + % Count number of #1's that we've seen. + \doignorecount = 0 + % + % Swallow text until we reach the matching `@end #1'. + \dodoignore{#1}% +} + +{ \catcode`_=11 % We want to use \_STOP_ which cannot appear in texinfo source. + \obeylines % + % + \gdef\dodoignore#1{% + % #1 contains the command name as a string, e.g., `ifinfo'. + % + % Define a command to find the next `@end #1'. + \long\def\doignoretext##1^^M@end #1{% + \doignoretextyyy##1^^M@#1\_STOP_}% + % + % And this command to find another #1 command, at the beginning of a + % line. (Otherwise, we would consider a line `@c @ifset', for + % example, to count as an @ifset for nesting.) + \long\def\doignoretextyyy##1^^M@#1##2\_STOP_{\doignoreyyy{##2}\_STOP_}% + % + % And now expand that command. + \doignoretext ^^M% + }% +} + +\def\doignoreyyy#1{% + \def\temp{#1}% + \ifx\temp\empty % Nothing found. + \let\next\doignoretextzzz + \else % Found a nested condition, ... + \advance\doignorecount by 1 + \let\next\doignoretextyyy % ..., look for another. + % If we're here, #1 ends with ^^M\ifinfo (for example). + \fi + \next #1% the token \_STOP_ is present just after this macro. +} + +% We have to swallow the remaining "\_STOP_". +% +\def\doignoretextzzz#1{% + \ifnum\doignorecount = 0 % We have just found the outermost @end. + \let\next\enddoignore + \else % Still inside a nested condition. + \advance\doignorecount by -1 + \let\next\doignoretext % Look for the next @end. + \fi + \next +} + +% Finish off ignored text. +{ \obeylines% + % Ignore anything after the last `@end #1'; this matters in verbatim + % environments, where otherwise the newline after an ignored conditional + % would result in a blank line in the output. + \gdef\enddoignore#1^^M{\endgroup\ignorespaces}% +} + + +% @set VAR sets the variable VAR to an empty value. +% @set VAR REST-OF-LINE sets VAR to the value REST-OF-LINE. +% +% Since we want to separate VAR from REST-OF-LINE (which might be +% empty), we can't just use \parsearg; we have to insert a space of our +% own to delimit the rest of the line, and then take it out again if we +% didn't need it. +% We rely on the fact that \parsearg sets \catcode`\ =10. +% +\parseargdef\set{\setyyy#1 \endsetyyy} +\def\setyyy#1 #2\endsetyyy{% + {% + \makevalueexpandable + \def\temp{#2}% + \edef\next{\gdef\makecsname{SET#1}}% + \ifx\temp\empty + \next{}% + \else + \setzzz#2\endsetzzz + \fi + }% +} +% Remove the trailing space \setxxx inserted. +\def\setzzz#1 \endsetzzz{\next{#1}} + +% @clear VAR clears (i.e., unsets) the variable VAR. +% +\parseargdef\clear{% + {% + \makevalueexpandable + \global\expandafter\let\csname SET#1\endcsname=\relax + }% +} + +% @value{foo} gets the text saved in variable foo. +\def\value{\begingroup\makevalueexpandable\valuexxx} +\def\valuexxx#1{\expandablevalue{#1}\endgroup} +{ + \catcode`\- = \active \catcode`\_ = \active + % + \gdef\makevalueexpandable{% + \let\value = \expandablevalue + % We don't want these characters active, ... + \catcode`\-=\other \catcode`\_=\other + % ..., but we might end up with active ones in the argument if + % we're called from @code, as @code{@value{foo-bar_}}, though. + % So \let them to their normal equivalents. + \let-\realdash \let_\normalunderscore + } +} + +% We have this subroutine so that we can handle at least some @value's +% properly in indexes (we call \makevalueexpandable in \indexdummies). +% The command has to be fully expandable (if the variable is set), since +% the result winds up in the index file. This means that if the +% variable's value contains other Texinfo commands, it's almost certain +% it will fail (although perhaps we could fix that with sufficient work +% to do a one-level expansion on the result, instead of complete). +% +\def\expandablevalue#1{% + \expandafter\ifx\csname SET#1\endcsname\relax + {[No value for ``#1'']}% + \message{Variable `#1', used in @value, is not set.}% + \else + \csname SET#1\endcsname + \fi +} + +% @ifset VAR ... @end ifset reads the `...' iff VAR has been defined +% with @set. +% +% To get special treatment of `@end ifset,' call \makeond and the redefine. +% +\makecond{ifset} +\def\ifset{\parsearg{\doifset{\let\next=\ifsetfail}}} +\def\doifset#1#2{% + {% + \makevalueexpandable + \let\next=\empty + \expandafter\ifx\csname SET#2\endcsname\relax + #1% If not set, redefine \next. + \fi + \expandafter + }\next +} +\def\ifsetfail{\doignore{ifset}} + +% @ifclear VAR ... @end ifclear reads the `...' iff VAR has never been +% defined with @set, or has been undefined with @clear. +% +% The `\else' inside the `\doifset' parameter is a trick to reuse the +% above code: if the variable is not set, do nothing, if it is set, +% then redefine \next to \ifclearfail. +% +\makecond{ifclear} +\def\ifclear{\parsearg{\doifset{\else \let\next=\ifclearfail}}} +\def\ifclearfail{\doignore{ifclear}} + +% @dircategory CATEGORY -- specify a category of the dir file +% which this file should belong to. Ignore this in TeX. +\let\dircategory=\comment + +% @defininfoenclose. +\let\definfoenclose=\comment + + +\message{indexing,} +% Index generation facilities + +% Define \newwrite to be identical to plain tex's \newwrite +% except not \outer, so it can be used within macros and \if's. +\edef\newwrite{\makecsname{ptexnewwrite}} + +% \newindex {foo} defines an index named foo. +% It automatically defines \fooindex such that +% \fooindex ...rest of line... puts an entry in the index foo. +% It also defines \fooindfile to be the number of the output channel for +% the file that accumulates this index. The file's extension is foo. +% The name of an index should be no more than 2 characters long +% for the sake of vms. +% +\def\newindex#1{% + \iflinks + \expandafter\newwrite \csname#1indfile\endcsname + \openout \csname#1indfile\endcsname \jobname.#1 % Open the file + \fi + \expandafter\xdef\csname#1index\endcsname{% % Define @#1index + \noexpand\doindex{#1}} +} + +% @defindex foo == \newindex{foo} +% +\def\defindex{\parsearg\newindex} + +% Define @defcodeindex, like @defindex except put all entries in @code. +% +\def\defcodeindex{\parsearg\newcodeindex} +% +\def\newcodeindex#1{% + \iflinks + \expandafter\newwrite \csname#1indfile\endcsname + \openout \csname#1indfile\endcsname \jobname.#1 + \fi + \expandafter\xdef\csname#1index\endcsname{% + \noexpand\docodeindex{#1}}% +} + + +% @synindex foo bar makes index foo feed into index bar. +% Do this instead of @defindex foo if you don't want it as a separate index. +% +% @syncodeindex foo bar similar, but put all entries made for index foo +% inside @code. +% +\def\synindex#1 #2 {\dosynindex\doindex{#1}{#2}} +\def\syncodeindex#1 #2 {\dosynindex\docodeindex{#1}{#2}} + +% #1 is \doindex or \docodeindex, #2 the index getting redefined (foo), +% #3 the target index (bar). +\def\dosynindex#1#2#3{% + % Only do \closeout if we haven't already done it, else we'll end up + % closing the target index. + \expandafter \ifx\csname donesynindex#2\endcsname \undefined + % The \closeout helps reduce unnecessary open files; the limit on the + % Acorn RISC OS is a mere 16 files. + \expandafter\closeout\csname#2indfile\endcsname + \expandafter\let\csname\donesynindex#2\endcsname = 1 + \fi + % redefine \fooindfile: + \expandafter\let\expandafter\temp\expandafter=\csname#3indfile\endcsname + \expandafter\let\csname#2indfile\endcsname=\temp + % redefine \fooindex: + \expandafter\xdef\csname#2index\endcsname{\noexpand#1{#3}}% +} + +% Define \doindex, the driver for all \fooindex macros. +% Argument #1 is generated by the calling \fooindex macro, +% and it is "foo", the name of the index. + +% \doindex just uses \parsearg; it calls \doind for the actual work. +% This is because \doind is more useful to call from other macros. + +% There is also \dosubind {index}{topic}{subtopic} +% which makes an entry in a two-level index such as the operation index. + +\def\doindex#1{\edef\indexname{#1}\parsearg\singleindexer} +\def\singleindexer #1{\doind{\indexname}{#1}} + +% like the previous two, but they put @code around the argument. +\def\docodeindex#1{\edef\indexname{#1}\parsearg\singlecodeindexer} +\def\singlecodeindexer #1{\doind{\indexname}{\code{#1}}} + +% Take care of Texinfo commands that can appear in an index entry. +% Since there are some commands we want to expand, and others we don't, +% we have to laboriously prevent expansion for those that we don't. +% +\def\indexdummies{% + \escapechar = `\\ % use backslash in output files. + \def\@{@}% change to @@ when we switch to @ as escape char in index files. + \def\ {\realbackslash\space }% + % + % Need these in case \tex is in effect and \{ is a \delimiter again. + % But can't use \lbracecmd and \rbracecmd because texindex assumes + % braces and backslashes are used only as delimiters. + \let\{ = \mylbrace + \let\} = \myrbrace + % + % I don't entirely understand this, but when an index entry is + % generated from a macro call, the \endinput which \scanmacro inserts + % causes processing to be prematurely terminated. This is, + % apparently, because \indexsorttmp is fully expanded, and \endinput + % is an expandable command. The redefinition below makes \endinput + % disappear altogether for that purpose -- although logging shows that + % processing continues to some further point. On the other hand, it + % seems \endinput does not hurt in the printed index arg, since that + % is still getting written without apparent harm. + % + % Sample source (mac-idx3.tex, reported by Graham Percival to + % help-texinfo, 22may06): + % @macro funindex {WORD} + % @findex xyz + % @end macro + % ... + % @funindex commtest + % + % The above is not enough to reproduce the bug, but it gives the flavor. + % + % Sample whatsit resulting: + % .@write3{\entry{xyz}{@folio }{@code {xyz@endinput }}} + % + % So: + \let\endinput = \empty + % + % Do the redefinitions. + \commondummies +} + +% For the aux and toc files, @ is the escape character. So we want to +% redefine everything using @ as the escape character (instead of +% \realbackslash, still used for index files). When everything uses @, +% this will be simpler. +% +\def\atdummies{% + \def\@{@@}% + \def\ {@ }% + \let\{ = \lbraceatcmd + \let\} = \rbraceatcmd + % + % Do the redefinitions. + \commondummies + \otherbackslash +} + +% Called from \indexdummies and \atdummies. +% +\def\commondummies{% + % + % \definedummyword defines \#1 as \string\#1\space, thus effectively + % preventing its expansion. This is used only for control% words, + % not control letters, because the \space would be incorrect for + % control characters, but is needed to separate the control word + % from whatever follows. + % + % For control letters, we have \definedummyletter, which omits the + % space. + % + % These can be used both for control words that take an argument and + % those that do not. If it is followed by {arg} in the input, then + % that will dutifully get written to the index (or wherever). + % + \def\definedummyword ##1{\def##1{\string##1\space}}% + \def\definedummyletter##1{\def##1{\string##1}}% + \let\definedummyaccent\definedummyletter + % + \commondummiesnofonts + % + \definedummyletter\_% + % + % Non-English letters. + \definedummyword\AA + \definedummyword\AE + \definedummyword\L + \definedummyword\OE + \definedummyword\O + \definedummyword\aa + \definedummyword\ae + \definedummyword\l + \definedummyword\oe + \definedummyword\o + \definedummyword\ss + \definedummyword\exclamdown + \definedummyword\questiondown + \definedummyword\ordf + \definedummyword\ordm + % + % Although these internal commands shouldn't show up, sometimes they do. + \definedummyword\bf + \definedummyword\gtr + \definedummyword\hat + \definedummyword\less + \definedummyword\sf + \definedummyword\sl + \definedummyword\tclose + \definedummyword\tt + % + \definedummyword\LaTeX + \definedummyword\TeX + % + % Assorted special characters. + \definedummyword\bullet + \definedummyword\comma + \definedummyword\copyright + \definedummyword\registeredsymbol + \definedummyword\dots + \definedummyword\enddots + \definedummyword\equiv + \definedummyword\error + \definedummyword\euro + \definedummyword\expansion + \definedummyword\minus + \definedummyword\pounds + \definedummyword\point + \definedummyword\print + \definedummyword\result + \definedummyword\textdegree + % + % We want to disable all macros so that they are not expanded by \write. + \macrolist + % + \normalturnoffactive + % + % Handle some cases of @value -- where it does not contain any + % (non-fully-expandable) commands. + \makevalueexpandable +} + +% \commondummiesnofonts: common to \commondummies and \indexnofonts. +% +\def\commondummiesnofonts{% + % Control letters and accents. + \definedummyletter\!% + \definedummyaccent\"% + \definedummyaccent\'% + \definedummyletter\*% + \definedummyaccent\,% + \definedummyletter\.% + \definedummyletter\/% + \definedummyletter\:% + \definedummyaccent\=% + \definedummyletter\?% + \definedummyaccent\^% + \definedummyaccent\`% + \definedummyaccent\~% + \definedummyword\u + \definedummyword\v + \definedummyword\H + \definedummyword\dotaccent + \definedummyword\ringaccent + \definedummyword\tieaccent + \definedummyword\ubaraccent + \definedummyword\udotaccent + \definedummyword\dotless + % + % Texinfo font commands. + \definedummyword\b + \definedummyword\i + \definedummyword\r + \definedummyword\sc + \definedummyword\t + % + % Commands that take arguments. + \definedummyword\acronym + \definedummyword\cite + \definedummyword\code + \definedummyword\command + \definedummyword\dfn + \definedummyword\emph + \definedummyword\env + \definedummyword\file + \definedummyword\kbd + \definedummyword\key + \definedummyword\math + \definedummyword\option + \definedummyword\pxref + \definedummyword\ref + \definedummyword\samp + \definedummyword\strong + \definedummyword\tie + \definedummyword\uref + \definedummyword\url + \definedummyword\var + \definedummyword\verb + \definedummyword\w + \definedummyword\xref +} + +% \indexnofonts is used when outputting the strings to sort the index +% by, and when constructing control sequence names. It eliminates all +% control sequences and just writes whatever the best ASCII sort string +% would be for a given command (usually its argument). +% +\def\indexnofonts{% + % Accent commands should become @asis. + \def\definedummyaccent##1{\let##1\asis}% + % We can just ignore other control letters. + \def\definedummyletter##1{\let##1\empty}% + % Hopefully, all control words can become @asis. + \let\definedummyword\definedummyaccent + % + \commondummiesnofonts + % + % Don't no-op \tt, since it isn't a user-level command + % and is used in the definitions of the active chars like <, >, |, etc. + % Likewise with the other plain tex font commands. + %\let\tt=\asis + % + \def\ { }% + \def\@{@}% + % how to handle braces? + \def\_{\normalunderscore}% + % + % Non-English letters. + \def\AA{AA}% + \def\AE{AE}% + \def\L{L}% + \def\OE{OE}% + \def\O{O}% + \def\aa{aa}% + \def\ae{ae}% + \def\l{l}% + \def\oe{oe}% + \def\o{o}% + \def\ss{ss}% + \def\exclamdown{!}% + \def\questiondown{?}% + \def\ordf{a}% + \def\ordm{o}% + % + \def\LaTeX{LaTeX}% + \def\TeX{TeX}% + % + % Assorted special characters. + % (The following {} will end up in the sort string, but that's ok.) + \def\bullet{bullet}% + \def\comma{,}% + \def\copyright{copyright}% + \def\registeredsymbol{R}% + \def\dots{...}% + \def\enddots{...}% + \def\equiv{==}% + \def\error{error}% + \def\euro{euro}% + \def\expansion{==>}% + \def\minus{-}% + \def\pounds{pounds}% + \def\point{.}% + \def\print{-|}% + \def\result{=>}% + \def\textdegree{degrees}% + % + % We need to get rid of all macros, leaving only the arguments (if present). + % Of course this is not nearly correct, but it is the best we can do for now. + % makeinfo does not expand macros in the argument to @deffn, which ends up + % writing an index entry, and texindex isn't prepared for an index sort entry + % that starts with \. + % + % Since macro invocations are followed by braces, we can just redefine them + % to take a single TeX argument. The case of a macro invocation that + % goes to end-of-line is not handled. + % + \macrolist +} + +\let\indexbackslash=0 %overridden during \printindex. +\let\SETmarginindex=\relax % put index entries in margin (undocumented)? + +% Most index entries go through here, but \dosubind is the general case. +% #1 is the index name, #2 is the entry text. +\def\doind#1#2{\dosubind{#1}{#2}{}} + +% Workhorse for all \fooindexes. +% #1 is name of index, #2 is stuff to put there, #3 is subentry -- +% empty if called from \doind, as we usually are (the main exception +% is with most defuns, which call us directly). +% +\def\dosubind#1#2#3{% + \iflinks + {% + % Store the main index entry text (including the third arg). + \toks0 = {#2}% + % If third arg is present, precede it with a space. + \def\thirdarg{#3}% + \ifx\thirdarg\empty \else + \toks0 = \expandafter{\the\toks0 \space #3}% + \fi + % + \edef\writeto{\csname#1indfile\endcsname}% + % + \ifvmode + \dosubindsanitize + \else + \dosubindwrite + \fi + }% + \fi +} + +% Write the entry in \toks0 to the index file: +% +\def\dosubindwrite{% + % Put the index entry in the margin if desired. + \ifx\SETmarginindex\relax\else + \insert\margin{\hbox{\vrule height8pt depth3pt width0pt \the\toks0}}% + \fi + % + % Remember, we are within a group. + \indexdummies % Must do this here, since \bf, etc expand at this stage + \def\backslashcurfont{\indexbackslash}% \indexbackslash isn't defined now + % so it will be output as is; and it will print as backslash. + % + % Process the index entry with all font commands turned off, to + % get the string to sort by. + {\indexnofonts + \edef\temp{\the\toks0}% need full expansion + \xdef\indexsorttmp{\temp}% + }% + % + % Set up the complete index entry, with both the sort key and + % the original text, including any font commands. We write + % three arguments to \entry to the .?? file (four in the + % subentry case), texindex reduces to two when writing the .??s + % sorted result. + \edef\temp{% + \write\writeto{% + \string\entry{\indexsorttmp}{\noexpand\folio}{\the\toks0}}% + }% + \temp +} + +% Take care of unwanted page breaks: +% +% If a skip is the last thing on the list now, preserve it +% by backing up by \lastskip, doing the \write, then inserting +% the skip again. Otherwise, the whatsit generated by the +% \write will make \lastskip zero. The result is that sequences +% like this: +% @end defun +% @tindex whatever +% @defun ... +% will have extra space inserted, because the \medbreak in the +% start of the @defun won't see the skip inserted by the @end of +% the previous defun. +% +% But don't do any of this if we're not in vertical mode. We +% don't want to do a \vskip and prematurely end a paragraph. +% +% Avoid page breaks due to these extra skips, too. +% +% But wait, there is a catch there: +% We'll have to check whether \lastskip is zero skip. \ifdim is not +% sufficient for this purpose, as it ignores stretch and shrink parts +% of the skip. The only way seems to be to check the textual +% representation of the skip. +% +% The following is almost like \def\zeroskipmacro{0.0pt} except that +% the ``p'' and ``t'' characters have catcode \other, not 11 (letter). +% +\edef\zeroskipmacro{\expandafter\the\csname z@skip\endcsname} +% +% ..., ready, GO: +% +\def\dosubindsanitize{% + % \lastskip and \lastpenalty cannot both be nonzero simultaneously. + \skip0 = \lastskip + \edef\lastskipmacro{\the\lastskip}% + \count255 = \lastpenalty + % + % If \lastskip is nonzero, that means the last item was a + % skip. And since a skip is discardable, that means this + % -\skip0 glue we're inserting is preceded by a + % non-discardable item, therefore it is not a potential + % breakpoint, therefore no \nobreak needed. + \ifx\lastskipmacro\zeroskipmacro + \else + \vskip-\skip0 + \fi + % + \dosubindwrite + % + \ifx\lastskipmacro\zeroskipmacro + % If \lastskip was zero, perhaps the last item was a penalty, and + % perhaps it was >=10000, e.g., a \nobreak. In that case, we want + % to re-insert the same penalty (values >10000 are used for various + % signals); since we just inserted a non-discardable item, any + % following glue (such as a \parskip) would be a breakpoint. For example: + % + % @deffn deffn-whatever + % @vindex index-whatever + % Description. + % would allow a break between the index-whatever whatsit + % and the "Description." paragraph. + \ifnum\count255>9999 \penalty\count255 \fi + \else + % On the other hand, if we had a nonzero \lastskip, + % this make-up glue would be preceded by a non-discardable item + % (the whatsit from the \write), so we must insert a \nobreak. + \nobreak\vskip\skip0 + \fi +} + +% The index entry written in the file actually looks like +% \entry {sortstring}{page}{topic} +% or +% \entry {sortstring}{page}{topic}{subtopic} +% The texindex program reads in these files and writes files +% containing these kinds of lines: +% \initial {c} +% before the first topic whose initial is c +% \entry {topic}{pagelist} +% for a topic that is used without subtopics +% \primary {topic} +% for the beginning of a topic that is used with subtopics +% \secondary {subtopic}{pagelist} +% for each subtopic. + +% Define the user-accessible indexing commands +% @findex, @vindex, @kindex, @cindex. + +\def\findex {\fnindex} +\def\kindex {\kyindex} +\def\cindex {\cpindex} +\def\vindex {\vrindex} +\def\tindex {\tpindex} +\def\pindex {\pgindex} + +\def\cindexsub {\begingroup\obeylines\cindexsub} +{\obeylines % +\gdef\cindexsub "#1" #2^^M{\endgroup % +\dosubind{cp}{#2}{#1}}} + +% Define the macros used in formatting output of the sorted index material. + +% @printindex causes a particular index (the ??s file) to get printed. +% It does not print any chapter heading (usually an @unnumbered). +% +\parseargdef\printindex{\begingroup + \dobreak \chapheadingskip{10000}% + % + \smallfonts \rm + \tolerance = 9500 + \everypar = {}% don't want the \kern\-parindent from indentation suppression. + % + % See if the index file exists and is nonempty. + % Change catcode of @ here so that if the index file contains + % \initial {@} + % as its first line, TeX doesn't complain about mismatched braces + % (because it thinks @} is a control sequence). + \catcode`\@ = 11 + \openin 1 \jobname.#1s + \ifeof 1 + % \enddoublecolumns gets confused if there is no text in the index, + % and it loses the chapter title and the aux file entries for the + % index. The easiest way to prevent this problem is to make sure + % there is some text. + \putwordIndexNonexistent + \else + % + % If the index file exists but is empty, then \openin leaves \ifeof + % false. We have to make TeX try to read something from the file, so + % it can discover if there is anything in it. + \read 1 to \temp + \ifeof 1 + \putwordIndexIsEmpty + \else + % Index files are almost Texinfo source, but we use \ as the escape + % character. It would be better to use @, but that's too big a change + % to make right now. + \def\indexbackslash{\backslashcurfont}% + \catcode`\\ = 0 + \escapechar = `\\ + \begindoublecolumns + \input \jobname.#1s + \enddoublecolumns + \fi + \fi + \closein 1 +\endgroup} + +% These macros are used by the sorted index file itself. +% Change them to control the appearance of the index. + +\def\initial#1{{% + % Some minor font changes for the special characters. + \let\tentt=\sectt \let\tt=\sectt \let\sf=\sectt + % + % Remove any glue we may have, we'll be inserting our own. + \removelastskip + % + % We like breaks before the index initials, so insert a bonus. + \nobreak + \vskip 0pt plus 3\baselineskip + \penalty 0 + \vskip 0pt plus -3\baselineskip + % + % Typeset the initial. Making this add up to a whole number of + % baselineskips increases the chance of the dots lining up from column + % to column. It still won't often be perfect, because of the stretch + % we need before each entry, but it's better. + % + % No shrink because it confuses \balancecolumns. + \vskip 1.67\baselineskip plus .5\baselineskip + \leftline{\secbf #1}% + % Do our best not to break after the initial. + \nobreak + \vskip .33\baselineskip plus .1\baselineskip +}} + +% \entry typesets a paragraph consisting of the text (#1), dot leaders, and +% then page number (#2) flushed to the right margin. It is used for index +% and table of contents entries. The paragraph is indented by \leftskip. +% +% A straightforward implementation would start like this: +% \def\entry#1#2{... +% But this frozes the catcodes in the argument, and can cause problems to +% @code, which sets - active. This problem was fixed by a kludge--- +% ``-'' was active throughout whole index, but this isn't really right. +% +% The right solution is to prevent \entry from swallowing the whole text. +% --kasal, 21nov03 +\def\entry{% + \begingroup + % + % Start a new paragraph if necessary, so our assignments below can't + % affect previous text. + \par + % + % Do not fill out the last line with white space. + \parfillskip = 0in + % + % No extra space above this paragraph. + \parskip = 0in + % + % Do not prefer a separate line ending with a hyphen to fewer lines. + \finalhyphendemerits = 0 + % + % \hangindent is only relevant when the entry text and page number + % don't both fit on one line. In that case, bob suggests starting the + % dots pretty far over on the line. Unfortunately, a large + % indentation looks wrong when the entry text itself is broken across + % lines. So we use a small indentation and put up with long leaders. + % + % \hangafter is reset to 1 (which is the value we want) at the start + % of each paragraph, so we need not do anything with that. + \hangindent = 2em + % + % When the entry text needs to be broken, just fill out the first line + % with blank space. + \rightskip = 0pt plus1fil + % + % A bit of stretch before each entry for the benefit of balancing + % columns. + \vskip 0pt plus1pt + % + % Swallow the left brace of the text (first parameter): + \afterassignment\doentry + \let\temp = +} +\def\doentry{% + \bgroup % Instead of the swallowed brace. + \noindent + \aftergroup\finishentry + % And now comes the text of the entry. +} +\def\finishentry#1{% + % #1 is the page number. + % + % The following is kludged to not output a line of dots in the index if + % there are no page numbers. The next person who breaks this will be + % cursed by a Unix daemon. + \def\tempa{{\rm }}% + \def\tempb{#1}% + \edef\tempc{\tempa}% + \edef\tempd{\tempb}% + \ifx\tempc\tempd + \ % + \else + % + % If we must, put the page number on a line of its own, and fill out + % this line with blank space. (The \hfil is overwhelmed with the + % fill leaders glue in \indexdotfill if the page number does fit.) + \hfil\penalty50 + \null\nobreak\indexdotfill % Have leaders before the page number. + % + % The `\ ' here is removed by the implicit \unskip that TeX does as + % part of (the primitive) \par. Without it, a spurious underfull + % \hbox ensues. + \ifpdf + \pdfgettoks#1.% + \ \the\toksA + \else + \ #1% + \fi + \fi + \par + \endgroup +} + +% Like plain.tex's \dotfill, except uses up at least 1 em. +\def\indexdotfill{\cleaders + \hbox{$\mathsurround=0pt \mkern1.5mu.\mkern1.5mu$}\hskip 1em plus 1fill} + +\def\primary #1{\line{#1\hfil}} + +\newskip\secondaryindent \secondaryindent=0.5cm +\def\secondary#1#2{{% + \parfillskip=0in + \parskip=0in + \hangindent=1in + \hangafter=1 + \noindent\hskip\secondaryindent\hbox{#1}\indexdotfill + \ifpdf + \pdfgettoks#2.\ \the\toksA % The page number ends the paragraph. + \else + #2 + \fi + \par +}} + +% Define two-column mode, which we use to typeset indexes. +% Adapted from the TeXbook, page 416, which is to say, +% the manmac.tex format used to print the TeXbook itself. +\catcode`\@=11 + +\newbox\partialpage +\newdimen\doublecolumnhsize + +\def\begindoublecolumns{\begingroup % ended by \enddoublecolumns + % Grab any single-column material above us. + \output = {% + % + % Here is a possibility not foreseen in manmac: if we accumulate a + % whole lot of material, we might end up calling this \output + % routine twice in a row (see the doublecol-lose test, which is + % essentially a couple of indexes with @setchapternewpage off). In + % that case we just ship out what is in \partialpage with the normal + % output routine. Generally, \partialpage will be empty when this + % runs and this will be a no-op. See the indexspread.tex test case. + \ifvoid\partialpage \else + \onepageout{\pagecontents\partialpage}% + \fi + % + \global\setbox\partialpage = \vbox{% + % Unvbox the main output page. + \unvbox\PAGE + \kern-\topskip \kern\baselineskip + }% + }% + \eject % run that output routine to set \partialpage + % + % Use the double-column output routine for subsequent pages. + \output = {\doublecolumnout}% + % + % Change the page size parameters. We could do this once outside this + % routine, in each of @smallbook, @afourpaper, and the default 8.5x11 + % format, but then we repeat the same computation. Repeating a couple + % of assignments once per index is clearly meaningless for the + % execution time, so we may as well do it in one place. + % + % First we halve the line length, less a little for the gutter between + % the columns. We compute the gutter based on the line length, so it + % changes automatically with the paper format. The magic constant + % below is chosen so that the gutter has the same value (well, +-<1pt) + % as it did when we hard-coded it. + % + % We put the result in a separate register, \doublecolumhsize, so we + % can restore it in \pagesofar, after \hsize itself has (potentially) + % been clobbered. + % + \doublecolumnhsize = \hsize + \advance\doublecolumnhsize by -.04154\hsize + \divide\doublecolumnhsize by 2 + \hsize = \doublecolumnhsize + % + % Double the \vsize as well. (We don't need a separate register here, + % since nobody clobbers \vsize.) + \vsize = 2\vsize +} + +% The double-column output routine for all double-column pages except +% the last. +% +\def\doublecolumnout{% + \splittopskip=\topskip \splitmaxdepth=\maxdepth + % Get the available space for the double columns -- the normal + % (undoubled) page height minus any material left over from the + % previous page. + \dimen@ = \vsize + \divide\dimen@ by 2 + \advance\dimen@ by -\ht\partialpage + % + % box0 will be the left-hand column, box2 the right. + \setbox0=\vsplit255 to\dimen@ \setbox2=\vsplit255 to\dimen@ + \onepageout\pagesofar + \unvbox255 + \penalty\outputpenalty +} +% +% Re-output the contents of the output page -- any previous material, +% followed by the two boxes we just split, in box0 and box2. +\def\pagesofar{% + \unvbox\partialpage + % + \hsize = \doublecolumnhsize + \wd0=\hsize \wd2=\hsize + \hbox to\pagewidth{\box0\hfil\box2}% +} +% +% All done with double columns. +\def\enddoublecolumns{% + \output = {% + % Split the last of the double-column material. Leave it on the + % current page, no automatic page break. + \balancecolumns + % + % If we end up splitting too much material for the current page, + % though, there will be another page break right after this \output + % invocation ends. Having called \balancecolumns once, we do not + % want to call it again. Therefore, reset \output to its normal + % definition right away. (We hope \balancecolumns will never be + % called on to balance too much material, but if it is, this makes + % the output somewhat more palatable.) + \global\output = {\onepageout{\pagecontents\PAGE}}% + }% + \eject + \endgroup % started in \begindoublecolumns + % + % \pagegoal was set to the doubled \vsize above, since we restarted + % the current page. We're now back to normal single-column + % typesetting, so reset \pagegoal to the normal \vsize (after the + % \endgroup where \vsize got restored). + \pagegoal = \vsize +} +% +% Called at the end of the double column material. +\def\balancecolumns{% + \setbox0 = \vbox{\unvbox255}% like \box255 but more efficient, see p.120. + \dimen@ = \ht0 + \advance\dimen@ by \topskip + \advance\dimen@ by-\baselineskip + \divide\dimen@ by 2 % target to split to + %debug\message{final 2-column material height=\the\ht0, target=\the\dimen@.}% + \splittopskip = \topskip + % Loop until we get a decent breakpoint. + {% + \vbadness = 10000 + \loop + \global\setbox3 = \copy0 + \global\setbox1 = \vsplit3 to \dimen@ + \ifdim\ht3>\dimen@ + \global\advance\dimen@ by 1pt + \repeat + }% + %debug\message{split to \the\dimen@, column heights: \the\ht1, \the\ht3.}% + \setbox0=\vbox to\dimen@{\unvbox1}% + \setbox2=\vbox to\dimen@{\unvbox3}% + % + \pagesofar +} +\catcode`\@ = \other + + +\message{sectioning,} +% Chapters, sections, etc. + +% \unnumberedno is an oxymoron, of course. But we count the unnumbered +% sections so that we can refer to them unambiguously in the pdf +% outlines by their "section number". We avoid collisions with chapter +% numbers by starting them at 10000. (If a document ever has 10000 +% chapters, we're in trouble anyway, I'm sure.) +\newcount\unnumberedno \unnumberedno = 10000 +\newcount\chapno +\newcount\secno \secno=0 +\newcount\subsecno \subsecno=0 +\newcount\subsubsecno \subsubsecno=0 + +% This counter is funny since it counts through charcodes of letters A, B, ... +\newcount\appendixno \appendixno = `\@ +% +% \def\appendixletter{\char\the\appendixno} +% We do the following ugly conditional instead of the above simple +% construct for the sake of pdftex, which needs the actual +% letter in the expansion, not just typeset. +% +\def\appendixletter{% + \ifnum\appendixno=`A A% + \else\ifnum\appendixno=`B B% + \else\ifnum\appendixno=`C C% + \else\ifnum\appendixno=`D D% + \else\ifnum\appendixno=`E E% + \else\ifnum\appendixno=`F F% + \else\ifnum\appendixno=`G G% + \else\ifnum\appendixno=`H H% + \else\ifnum\appendixno=`I I% + \else\ifnum\appendixno=`J J% + \else\ifnum\appendixno=`K K% + \else\ifnum\appendixno=`L L% + \else\ifnum\appendixno=`M M% + \else\ifnum\appendixno=`N N% + \else\ifnum\appendixno=`O O% + \else\ifnum\appendixno=`P P% + \else\ifnum\appendixno=`Q Q% + \else\ifnum\appendixno=`R R% + \else\ifnum\appendixno=`S S% + \else\ifnum\appendixno=`T T% + \else\ifnum\appendixno=`U U% + \else\ifnum\appendixno=`V V% + \else\ifnum\appendixno=`W W% + \else\ifnum\appendixno=`X X% + \else\ifnum\appendixno=`Y Y% + \else\ifnum\appendixno=`Z Z% + % The \the is necessary, despite appearances, because \appendixletter is + % expanded while writing the .toc file. \char\appendixno is not + % expandable, thus it is written literally, thus all appendixes come out + % with the same letter (or @) in the toc without it. + \else\char\the\appendixno + \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi + \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi} + +% Each @chapter defines this as the name of the chapter. +% page headings and footings can use it. @section does likewise. +% However, they are not reliable, because we don't use marks. +\def\thischapter{} +\def\thissection{} + +\newcount\absseclevel % used to calculate proper heading level +\newcount\secbase\secbase=0 % @raisesections/@lowersections modify this count + +% @raisesections: treat @section as chapter, @subsection as section, etc. +\def\raisesections{\global\advance\secbase by -1} +\let\up=\raisesections % original BFox name + +% @lowersections: treat @chapter as section, @section as subsection, etc. +\def\lowersections{\global\advance\secbase by 1} +\let\down=\lowersections % original BFox name + +% we only have subsub. +\chardef\maxseclevel = 3 +% +% A numbered section within an unnumbered changes to unnumbered too. +% To achive this, remember the "biggest" unnum. sec. we are currently in: +\chardef\unmlevel = \maxseclevel +% +% Trace whether the current chapter is an appendix or not: +% \chapheadtype is "N" or "A", unnumbered chapters are ignored. +\def\chapheadtype{N} + +% Choose a heading macro +% #1 is heading type +% #2 is heading level +% #3 is text for heading +\def\genhead#1#2#3{% + % Compute the abs. sec. level: + \absseclevel=#2 + \advance\absseclevel by \secbase + % Make sure \absseclevel doesn't fall outside the range: + \ifnum \absseclevel < 0 + \absseclevel = 0 + \else + \ifnum \absseclevel > 3 + \absseclevel = 3 + \fi + \fi + % The heading type: + \def\headtype{#1}% + \if \headtype U% + \ifnum \absseclevel < \unmlevel + \chardef\unmlevel = \absseclevel + \fi + \else + % Check for appendix sections: + \ifnum \absseclevel = 0 + \edef\chapheadtype{\headtype}% + \else + \if \headtype A\if \chapheadtype N% + \errmessage{@appendix... within a non-appendix chapter}% + \fi\fi + \fi + % Check for numbered within unnumbered: + \ifnum \absseclevel > \unmlevel + \def\headtype{U}% + \else + \chardef\unmlevel = 3 + \fi + \fi + % Now print the heading: + \if \headtype U% + \ifcase\absseclevel + \unnumberedzzz{#3}% + \or \unnumberedseczzz{#3}% + \or \unnumberedsubseczzz{#3}% + \or \unnumberedsubsubseczzz{#3}% + \fi + \else + \if \headtype A% + \ifcase\absseclevel + \appendixzzz{#3}% + \or \appendixsectionzzz{#3}% + \or \appendixsubseczzz{#3}% + \or \appendixsubsubseczzz{#3}% + \fi + \else + \ifcase\absseclevel + \chapterzzz{#3}% + \or \seczzz{#3}% + \or \numberedsubseczzz{#3}% + \or \numberedsubsubseczzz{#3}% + \fi + \fi + \fi + \suppressfirstparagraphindent +} + +% an interface: +\def\numhead{\genhead N} +\def\apphead{\genhead A} +\def\unnmhead{\genhead U} + +% @chapter, @appendix, @unnumbered. Increment top-level counter, reset +% all lower-level sectioning counters to zero. +% +% Also set \chaplevelprefix, which we prepend to @float sequence numbers +% (e.g., figures), q.v. By default (before any chapter), that is empty. +\let\chaplevelprefix = \empty +% +\outer\parseargdef\chapter{\numhead0{#1}} % normally numhead0 calls chapterzzz +\def\chapterzzz#1{% + % section resetting is \global in case the chapter is in a group, such + % as an @include file. + \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 + \global\advance\chapno by 1 + % + % Used for \float. + \gdef\chaplevelprefix{\the\chapno.}% + \resetallfloatnos + % + \message{\putwordChapter\space \the\chapno}% + % + % Write the actual heading. + \chapmacro{#1}{Ynumbered}{\the\chapno}% + % + % So @section and the like are numbered underneath this chapter. + \global\let\section = \numberedsec + \global\let\subsection = \numberedsubsec + \global\let\subsubsection = \numberedsubsubsec +} + +\outer\parseargdef\appendix{\apphead0{#1}} % normally apphead0 calls appendixzzz +\def\appendixzzz#1{% + \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 + \global\advance\appendixno by 1 + \gdef\chaplevelprefix{\appendixletter.}% + \resetallfloatnos + % + \def\appendixnum{\putwordAppendix\space \appendixletter}% + \message{\appendixnum}% + % + \chapmacro{#1}{Yappendix}{\appendixletter}% + % + \global\let\section = \appendixsec + \global\let\subsection = \appendixsubsec + \global\let\subsubsection = \appendixsubsubsec +} + +\outer\parseargdef\unnumbered{\unnmhead0{#1}} % normally unnmhead0 calls unnumberedzzz +\def\unnumberedzzz#1{% + \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 + \global\advance\unnumberedno by 1 + % + % Since an unnumbered has no number, no prefix for figures. + \global\let\chaplevelprefix = \empty + \resetallfloatnos + % + % This used to be simply \message{#1}, but TeX fully expands the + % argument to \message. Therefore, if #1 contained @-commands, TeX + % expanded them. For example, in `@unnumbered The @cite{Book}', TeX + % expanded @cite (which turns out to cause errors because \cite is meant + % to be executed, not expanded). + % + % Anyway, we don't want the fully-expanded definition of @cite to appear + % as a result of the \message, we just want `@cite' itself. We use + % \the to achieve this: TeX expands \the only once, + % simply yielding the contents of . (We also do this for + % the toc entries.) + \toks0 = {#1}% + \message{(\the\toks0)}% + % + \chapmacro{#1}{Ynothing}{\the\unnumberedno}% + % + \global\let\section = \unnumberedsec + \global\let\subsection = \unnumberedsubsec + \global\let\subsubsection = \unnumberedsubsubsec +} + +% @centerchap is like @unnumbered, but the heading is centered. +\outer\parseargdef\centerchap{% + % Well, we could do the following in a group, but that would break + % an assumption that \chapmacro is called at the outermost level. + % Thus we are safer this way: --kasal, 24feb04 + \let\centerparametersmaybe = \centerparameters + \unnmhead0{#1}% + \let\centerparametersmaybe = \relax +} + +% @top is like @unnumbered. +\let\top\unnumbered + +% Sections. +\outer\parseargdef\numberedsec{\numhead1{#1}} % normally calls seczzz +\def\seczzz#1{% + \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 + \sectionheading{#1}{sec}{Ynumbered}{\the\chapno.\the\secno}% +} + +\outer\parseargdef\appendixsection{\apphead1{#1}} % normally calls appendixsectionzzz +\def\appendixsectionzzz#1{% + \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 + \sectionheading{#1}{sec}{Yappendix}{\appendixletter.\the\secno}% +} +\let\appendixsec\appendixsection + +\outer\parseargdef\unnumberedsec{\unnmhead1{#1}} % normally calls unnumberedseczzz +\def\unnumberedseczzz#1{% + \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 + \sectionheading{#1}{sec}{Ynothing}{\the\unnumberedno.\the\secno}% +} + +% Subsections. +\outer\parseargdef\numberedsubsec{\numhead2{#1}} % normally calls numberedsubseczzz +\def\numberedsubseczzz#1{% + \global\subsubsecno=0 \global\advance\subsecno by 1 + \sectionheading{#1}{subsec}{Ynumbered}{\the\chapno.\the\secno.\the\subsecno}% +} + +\outer\parseargdef\appendixsubsec{\apphead2{#1}} % normally calls appendixsubseczzz +\def\appendixsubseczzz#1{% + \global\subsubsecno=0 \global\advance\subsecno by 1 + \sectionheading{#1}{subsec}{Yappendix}% + {\appendixletter.\the\secno.\the\subsecno}% +} + +\outer\parseargdef\unnumberedsubsec{\unnmhead2{#1}} %normally calls unnumberedsubseczzz +\def\unnumberedsubseczzz#1{% + \global\subsubsecno=0 \global\advance\subsecno by 1 + \sectionheading{#1}{subsec}{Ynothing}% + {\the\unnumberedno.\the\secno.\the\subsecno}% +} + +% Subsubsections. +\outer\parseargdef\numberedsubsubsec{\numhead3{#1}} % normally numberedsubsubseczzz +\def\numberedsubsubseczzz#1{% + \global\advance\subsubsecno by 1 + \sectionheading{#1}{subsubsec}{Ynumbered}% + {\the\chapno.\the\secno.\the\subsecno.\the\subsubsecno}% +} + +\outer\parseargdef\appendixsubsubsec{\apphead3{#1}} % normally appendixsubsubseczzz +\def\appendixsubsubseczzz#1{% + \global\advance\subsubsecno by 1 + \sectionheading{#1}{subsubsec}{Yappendix}% + {\appendixletter.\the\secno.\the\subsecno.\the\subsubsecno}% +} + +\outer\parseargdef\unnumberedsubsubsec{\unnmhead3{#1}} %normally unnumberedsubsubseczzz +\def\unnumberedsubsubseczzz#1{% + \global\advance\subsubsecno by 1 + \sectionheading{#1}{subsubsec}{Ynothing}% + {\the\unnumberedno.\the\secno.\the\subsecno.\the\subsubsecno}% +} + +% These macros control what the section commands do, according +% to what kind of chapter we are in (ordinary, appendix, or unnumbered). +% Define them by default for a numbered chapter. +\let\section = \numberedsec +\let\subsection = \numberedsubsec +\let\subsubsection = \numberedsubsubsec + +% Define @majorheading, @heading and @subheading + +% NOTE on use of \vbox for chapter headings, section headings, and such: +% 1) We use \vbox rather than the earlier \line to permit +% overlong headings to fold. +% 2) \hyphenpenalty is set to 10000 because hyphenation in a +% heading is obnoxious; this forbids it. +% 3) Likewise, headings look best if no \parindent is used, and +% if justification is not attempted. Hence \raggedright. + + +\def\majorheading{% + {\advance\chapheadingskip by 10pt \chapbreak }% + \parsearg\chapheadingzzz +} + +\def\chapheading{\chapbreak \parsearg\chapheadingzzz} +\def\chapheadingzzz#1{% + {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 + \parindent=0pt\raggedright + \rm #1\hfill}}% + \bigskip \par\penalty 200\relax + \suppressfirstparagraphindent +} + +% @heading, @subheading, @subsubheading. +\parseargdef\heading{\sectionheading{#1}{sec}{Yomitfromtoc}{} + \suppressfirstparagraphindent} +\parseargdef\subheading{\sectionheading{#1}{subsec}{Yomitfromtoc}{} + \suppressfirstparagraphindent} +\parseargdef\subsubheading{\sectionheading{#1}{subsubsec}{Yomitfromtoc}{} + \suppressfirstparagraphindent} + +% These macros generate a chapter, section, etc. heading only +% (including whitespace, linebreaking, etc. around it), +% given all the information in convenient, parsed form. + +%%% Args are the skip and penalty (usually negative) +\def\dobreak#1#2{\par\ifdim\lastskip<#1\removelastskip\penalty#2\vskip#1\fi} + +%%% Define plain chapter starts, and page on/off switching for it +% Parameter controlling skip before chapter headings (if needed) + +\newskip\chapheadingskip + +\def\chapbreak{\dobreak \chapheadingskip {-4000}} +\def\chappager{\par\vfill\supereject} +\def\chapoddpage{\chappager \ifodd\pageno \else \hbox to 0pt{} \chappager\fi} + +\def\setchapternewpage #1 {\csname CHAPPAG#1\endcsname} + +\def\CHAPPAGoff{% +\global\let\contentsalignmacro = \chappager +\global\let\pchapsepmacro=\chapbreak +\global\let\pagealignmacro=\chappager} + +\def\CHAPPAGon{% +\global\let\contentsalignmacro = \chappager +\global\let\pchapsepmacro=\chappager +\global\let\pagealignmacro=\chappager +\global\def\HEADINGSon{\HEADINGSsingle}} + +\def\CHAPPAGodd{% +\global\let\contentsalignmacro = \chapoddpage +\global\let\pchapsepmacro=\chapoddpage +\global\let\pagealignmacro=\chapoddpage +\global\def\HEADINGSon{\HEADINGSdouble}} + +\CHAPPAGon + +% Chapter opening. +% +% #1 is the text, #2 is the section type (Ynumbered, Ynothing, +% Yappendix, Yomitfromtoc), #3 the chapter number. +% +% To test against our argument. +\def\Ynothingkeyword{Ynothing} +\def\Yomitfromtockeyword{Yomitfromtoc} +\def\Yappendixkeyword{Yappendix} +% +\def\chapmacro#1#2#3{% + \pchapsepmacro + {% + \chapfonts \rm + % + % Have to define \thissection before calling \donoderef, because the + % xref code eventually uses it. On the other hand, it has to be called + % after \pchapsepmacro, or the headline will change too soon. + \gdef\thissection{#1}% + \gdef\thischaptername{#1}% + % + % Only insert the separating space if we have a chapter/appendix + % number, and don't print the unnumbered ``number''. + \def\temptype{#2}% + \ifx\temptype\Ynothingkeyword + \setbox0 = \hbox{}% + \def\toctype{unnchap}% + \gdef\thischapternum{}% + \gdef\thischapter{#1}% + \else\ifx\temptype\Yomitfromtockeyword + \setbox0 = \hbox{}% contents like unnumbered, but no toc entry + \def\toctype{omit}% + \gdef\thischapternum{}% + \gdef\thischapter{}% + \else\ifx\temptype\Yappendixkeyword + \setbox0 = \hbox{\putwordAppendix{} #3\enspace}% + \def\toctype{app}% + \xdef\thischapternum{\appendixletter}% + % We don't substitute the actual chapter name into \thischapter + % because we don't want its macros evaluated now. And we don't + % use \thissection because that changes with each section. + % + \xdef\thischapter{\putwordAppendix{} \appendixletter: + \noexpand\thischaptername}% + \else + \setbox0 = \hbox{#3\enspace}% + \def\toctype{numchap}% + \xdef\thischapternum{\the\chapno}% + \xdef\thischapter{\putwordChapter{} \the\chapno: + \noexpand\thischaptername}% + \fi\fi\fi + % + % Write the toc entry for this chapter. Must come before the + % \donoderef, because we include the current node name in the toc + % entry, and \donoderef resets it to empty. + \writetocentry{\toctype}{#1}{#3}% + % + % For pdftex, we have to write out the node definition (aka, make + % the pdfdest) after any page break, but before the actual text has + % been typeset. If the destination for the pdf outline is after the + % text, then jumping from the outline may wind up with the text not + % being visible, for instance under high magnification. + \donoderef{#2}% + % + % Typeset the actual heading. + \vbox{\hyphenpenalty=10000 \tolerance=5000 \parindent=0pt \raggedright + \hangindent=\wd0 \centerparametersmaybe + \unhbox0 #1\par}% + }% + \nobreak\bigskip % no page break after a chapter title + \nobreak +} + +% @centerchap -- centered and unnumbered. +\let\centerparametersmaybe = \relax +\def\centerparameters{% + \advance\rightskip by 3\rightskip + \leftskip = \rightskip + \parfillskip = 0pt +} + + +% I don't think this chapter style is supported any more, so I'm not +% updating it with the new noderef stuff. We'll see. --karl, 11aug03. +% +\def\setchapterstyle #1 {\csname CHAPF#1\endcsname} +% +\def\unnchfopen #1{% +\chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 + \parindent=0pt\raggedright + \rm #1\hfill}}\bigskip \par\nobreak +} +\def\chfopen #1#2{\chapoddpage {\chapfonts +\vbox to 3in{\vfil \hbox to\hsize{\hfil #2} \hbox to\hsize{\hfil #1} \vfil}}% +\par\penalty 5000 % +} +\def\centerchfopen #1{% +\chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 + \parindent=0pt + \hfill {\rm #1}\hfill}}\bigskip \par\nobreak +} +\def\CHAPFopen{% + \global\let\chapmacro=\chfopen + \global\let\centerchapmacro=\centerchfopen} + + +% Section titles. These macros combine the section number parts and +% call the generic \sectionheading to do the printing. +% +\newskip\secheadingskip +\def\secheadingbreak{\dobreak \secheadingskip{-1000}} + +% Subsection titles. +\newskip\subsecheadingskip +\def\subsecheadingbreak{\dobreak \subsecheadingskip{-500}} + +% Subsubsection titles. +\def\subsubsecheadingskip{\subsecheadingskip} +\def\subsubsecheadingbreak{\subsecheadingbreak} + + +% Print any size, any type, section title. +% +% #1 is the text, #2 is the section level (sec/subsec/subsubsec), #3 is +% the section type for xrefs (Ynumbered, Ynothing, Yappendix), #4 is the +% section number. +% +\def\sectionheading#1#2#3#4{% + {% + % Switch to the right set of fonts. + \csname #2fonts\endcsname \rm + % + % Insert space above the heading. + \csname #2headingbreak\endcsname + % + % Only insert the space after the number if we have a section number. + \def\sectionlevel{#2}% + \def\temptype{#3}% + % + \ifx\temptype\Ynothingkeyword + \setbox0 = \hbox{}% + \def\toctype{unn}% + \gdef\thissection{#1}% + \else\ifx\temptype\Yomitfromtockeyword + % for @headings -- no section number, don't include in toc, + % and don't redefine \thissection. + \setbox0 = \hbox{}% + \def\toctype{omit}% + \let\sectionlevel=\empty + \else\ifx\temptype\Yappendixkeyword + \setbox0 = \hbox{#4\enspace}% + \def\toctype{app}% + \gdef\thissection{#1}% + \else + \setbox0 = \hbox{#4\enspace}% + \def\toctype{num}% + \gdef\thissection{#1}% + \fi\fi\fi + % + % Write the toc entry (before \donoderef). See comments in \chapmacro. + \writetocentry{\toctype\sectionlevel}{#1}{#4}% + % + % Write the node reference (= pdf destination for pdftex). + % Again, see comments in \chapmacro. + \donoderef{#3}% + % + % Interline glue will be inserted when the vbox is completed. + % That glue will be a valid breakpoint for the page, since it'll be + % preceded by a whatsit (usually from the \donoderef, or from the + % \writetocentry if there was no node). We don't want to allow that + % break, since then the whatsits could end up on page n while the + % section is on page n+1, thus toc/etc. are wrong. Debian bug 276000. + \nobreak + % + % Output the actual section heading. + \vbox{\hyphenpenalty=10000 \tolerance=5000 \parindent=0pt \raggedright + \hangindent=\wd0 % zero if no section number + \unhbox0 #1}% + }% + % Add extra space after the heading -- half of whatever came above it. + % Don't allow stretch, though. + \kern .5 \csname #2headingskip\endcsname + % + % Do not let the kern be a potential breakpoint, as it would be if it + % was followed by glue. + \nobreak + % + % We'll almost certainly start a paragraph next, so don't let that + % glue accumulate. (Not a breakpoint because it's preceded by a + % discardable item.) + \vskip-\parskip + % + % This is purely so the last item on the list is a known \penalty > + % 10000. This is so \startdefun can avoid allowing breakpoints after + % section headings. Otherwise, it would insert a valid breakpoint between: + % + % @section sec-whatever + % @deffn def-whatever + \penalty 10001 +} + + +\message{toc,} +% Table of contents. +\newwrite\tocfile + +% Write an entry to the toc file, opening it if necessary. +% Called from @chapter, etc. +% +% Example usage: \writetocentry{sec}{Section Name}{\the\chapno.\the\secno} +% We append the current node name (if any) and page number as additional +% arguments for the \{chap,sec,...}entry macros which will eventually +% read this. The node name is used in the pdf outlines as the +% destination to jump to. +% +% We open the .toc file for writing here instead of at @setfilename (or +% any other fixed time) so that @contents can be anywhere in the document. +% But if #1 is `omit', then we don't do anything. This is used for the +% table of contents chapter openings themselves. +% +\newif\iftocfileopened +\def\omitkeyword{omit}% +% +\def\writetocentry#1#2#3{% + \edef\writetoctype{#1}% + \ifx\writetoctype\omitkeyword \else + \iftocfileopened\else + \immediate\openout\tocfile = \jobname.toc + \global\tocfileopenedtrue + \fi + % + \iflinks + {\atdummies + \edef\temp{% + \write\tocfile{@#1entry{#2}{#3}{\lastnode}{\noexpand\folio}}}% + \temp + }% + \fi + \fi + % + % Tell \shipout to create a pdf destination on each page, if we're + % writing pdf. These are used in the table of contents. We can't + % just write one on every page because the title pages are numbered + % 1 and 2 (the page numbers aren't printed), and so are the first + % two pages of the document. Thus, we'd have two destinations named + % `1', and two named `2'. + \ifpdf \global\pdfmakepagedesttrue \fi +} + + +% These characters do not print properly in the Computer Modern roman +% fonts, so we must take special care. This is more or less redundant +% with the Texinfo input format setup at the end of this file. +% +\def\activecatcodes{% + \catcode`\"=\active + \catcode`\$=\active + \catcode`\<=\active + \catcode`\>=\active + \catcode`\\=\active + \catcode`\^=\active + \catcode`\_=\active + \catcode`\|=\active + \catcode`\~=\active +} + + +% Read the toc file, which is essentially Texinfo input. +\def\readtocfile{% + \setupdatafile + \activecatcodes + \input \jobname.toc +} + +\newskip\contentsrightmargin \contentsrightmargin=1in +\newcount\savepageno +\newcount\lastnegativepageno \lastnegativepageno = -1 + +% Prepare to read what we've written to \tocfile. +% +\def\startcontents#1{% + % If @setchapternewpage on, and @headings double, the contents should + % start on an odd page, unlike chapters. Thus, we maintain + % \contentsalignmacro in parallel with \pagealignmacro. + % From: Torbjorn Granlund + \contentsalignmacro + \immediate\closeout\tocfile + % + % Don't need to put `Contents' or `Short Contents' in the headline. + % It is abundantly clear what they are. + \def\thischapter{}% + \chapmacro{#1}{Yomitfromtoc}{}% + % + \savepageno = \pageno + \begingroup % Set up to handle contents files properly. + \raggedbottom % Worry more about breakpoints than the bottom. + \advance\hsize by -\contentsrightmargin % Don't use the full line length. + % + % Roman numerals for page numbers. + \ifnum \pageno>0 \global\pageno = \lastnegativepageno \fi +} + + +% Normal (long) toc. +\def\contents{% + \startcontents{\putwordTOC}% + \openin 1 \jobname.toc + \ifeof 1 \else + \readtocfile + \fi + \vfill \eject + \contentsalignmacro % in case @setchapternewpage odd is in effect + \ifeof 1 \else + \pdfmakeoutlines + \fi + \closein 1 + \endgroup + \lastnegativepageno = \pageno + \global\pageno = \savepageno +} + +% And just the chapters. +\def\summarycontents{% + \startcontents{\putwordShortTOC}% + % + \let\numchapentry = \shortchapentry + \let\appentry = \shortchapentry + \let\unnchapentry = \shortunnchapentry + % We want a true roman here for the page numbers. + \secfonts + \let\rm=\shortcontrm \let\bf=\shortcontbf + \let\sl=\shortcontsl \let\tt=\shortconttt + \rm + \hyphenpenalty = 10000 + \advance\baselineskip by 1pt % Open it up a little. + \def\numsecentry##1##2##3##4{} + \let\appsecentry = \numsecentry + \let\unnsecentry = \numsecentry + \let\numsubsecentry = \numsecentry + \let\appsubsecentry = \numsecentry + \let\unnsubsecentry = \numsecentry + \let\numsubsubsecentry = \numsecentry + \let\appsubsubsecentry = \numsecentry + \let\unnsubsubsecentry = \numsecentry + \openin 1 \jobname.toc + \ifeof 1 \else + \readtocfile + \fi + \closein 1 + \vfill \eject + \contentsalignmacro % in case @setchapternewpage odd is in effect + \endgroup + \lastnegativepageno = \pageno + \global\pageno = \savepageno +} +\let\shortcontents = \summarycontents + +% Typeset the label for a chapter or appendix for the short contents. +% The arg is, e.g., `A' for an appendix, or `3' for a chapter. +% +\def\shortchaplabel#1{% + % This space should be enough, since a single number is .5em, and the + % widest letter (M) is 1em, at least in the Computer Modern fonts. + % But use \hss just in case. + % (This space doesn't include the extra space that gets added after + % the label; that gets put in by \shortchapentry above.) + % + % We'd like to right-justify chapter numbers, but that looks strange + % with appendix letters. And right-justifying numbers and + % left-justifying letters looks strange when there is less than 10 + % chapters. Have to read the whole toc once to know how many chapters + % there are before deciding ... + \hbox to 1em{#1\hss}% +} + +% These macros generate individual entries in the table of contents. +% The first argument is the chapter or section name. +% The last argument is the page number. +% The arguments in between are the chapter number, section number, ... + +% Chapters, in the main contents. +\def\numchapentry#1#2#3#4{\dochapentry{#2\labelspace#1}{#4}} +% +% Chapters, in the short toc. +% See comments in \dochapentry re vbox and related settings. +\def\shortchapentry#1#2#3#4{% + \tocentry{\shortchaplabel{#2}\labelspace #1}{\doshortpageno\bgroup#4\egroup}% +} + +% Appendices, in the main contents. +% Need the word Appendix, and a fixed-size box. +% +\def\appendixbox#1{% + % We use M since it's probably the widest letter. + \setbox0 = \hbox{\putwordAppendix{} M}% + \hbox to \wd0{\putwordAppendix{} #1\hss}} +% +\def\appentry#1#2#3#4{\dochapentry{\appendixbox{#2}\labelspace#1}{#4}} + +% Unnumbered chapters. +\def\unnchapentry#1#2#3#4{\dochapentry{#1}{#4}} +\def\shortunnchapentry#1#2#3#4{\tocentry{#1}{\doshortpageno\bgroup#4\egroup}} + +% Sections. +\def\numsecentry#1#2#3#4{\dosecentry{#2\labelspace#1}{#4}} +\let\appsecentry=\numsecentry +\def\unnsecentry#1#2#3#4{\dosecentry{#1}{#4}} + +% Subsections. +\def\numsubsecentry#1#2#3#4{\dosubsecentry{#2\labelspace#1}{#4}} +\let\appsubsecentry=\numsubsecentry +\def\unnsubsecentry#1#2#3#4{\dosubsecentry{#1}{#4}} + +% And subsubsections. +\def\numsubsubsecentry#1#2#3#4{\dosubsubsecentry{#2\labelspace#1}{#4}} +\let\appsubsubsecentry=\numsubsubsecentry +\def\unnsubsubsecentry#1#2#3#4{\dosubsubsecentry{#1}{#4}} + +% This parameter controls the indentation of the various levels. +% Same as \defaultparindent. +\newdimen\tocindent \tocindent = 15pt + +% Now for the actual typesetting. In all these, #1 is the text and #2 is the +% page number. +% +% If the toc has to be broken over pages, we want it to be at chapters +% if at all possible; hence the \penalty. +\def\dochapentry#1#2{% + \penalty-300 \vskip1\baselineskip plus.33\baselineskip minus.25\baselineskip + \begingroup + \chapentryfonts + \tocentry{#1}{\dopageno\bgroup#2\egroup}% + \endgroup + \nobreak\vskip .25\baselineskip plus.1\baselineskip +} + +\def\dosecentry#1#2{\begingroup + \secentryfonts \leftskip=\tocindent + \tocentry{#1}{\dopageno\bgroup#2\egroup}% +\endgroup} + +\def\dosubsecentry#1#2{\begingroup + \subsecentryfonts \leftskip=2\tocindent + \tocentry{#1}{\dopageno\bgroup#2\egroup}% +\endgroup} + +\def\dosubsubsecentry#1#2{\begingroup + \subsubsecentryfonts \leftskip=3\tocindent + \tocentry{#1}{\dopageno\bgroup#2\egroup}% +\endgroup} + +% We use the same \entry macro as for the index entries. +\let\tocentry = \entry + +% Space between chapter (or whatever) number and the title. +\def\labelspace{\hskip1em \relax} + +\def\dopageno#1{{\rm #1}} +\def\doshortpageno#1{{\rm #1}} + +\def\chapentryfonts{\secfonts \rm} +\def\secentryfonts{\textfonts} +\def\subsecentryfonts{\textfonts} +\def\subsubsecentryfonts{\textfonts} + + +\message{environments,} +% @foo ... @end foo. + +% @point{}, @result{}, @expansion{}, @print{}, @equiv{}. +% +% Since these characters are used in examples, it should be an even number of +% \tt widths. Each \tt character is 1en, so two makes it 1em. +% +\def\point{$\star$} +\def\result{\leavevmode\raise.15ex\hbox to 1em{\hfil$\Rightarrow$\hfil}} +\def\expansion{\leavevmode\raise.1ex\hbox to 1em{\hfil$\mapsto$\hfil}} +\def\print{\leavevmode\lower.1ex\hbox to 1em{\hfil$\dashv$\hfil}} +\def\equiv{\leavevmode\lower.1ex\hbox to 1em{\hfil$\ptexequiv$\hfil}} + +% The @error{} command. +% Adapted from the TeXbook's \boxit. +% +\newbox\errorbox +% +{\tentt \global\dimen0 = 3em}% Width of the box. +\dimen2 = .55pt % Thickness of rules +% The text. (`r' is open on the right, `e' somewhat less so on the left.) +\setbox0 = \hbox{\kern-.75pt \reducedsf error\kern-1.5pt} +% +\setbox\errorbox=\hbox to \dimen0{\hfil + \hsize = \dimen0 \advance\hsize by -5.8pt % Space to left+right. + \advance\hsize by -2\dimen2 % Rules. + \vbox{% + \hrule height\dimen2 + \hbox{\vrule width\dimen2 \kern3pt % Space to left of text. + \vtop{\kern2.4pt \box0 \kern2.4pt}% Space above/below. + \kern3pt\vrule width\dimen2}% Space to right. + \hrule height\dimen2} + \hfil} +% +\def\error{\leavevmode\lower.7ex\copy\errorbox} + +% @tex ... @end tex escapes into raw Tex temporarily. +% One exception: @ is still an escape character, so that @end tex works. +% But \@ or @@ will get a plain tex @ character. + +\envdef\tex{% + \catcode `\\=0 \catcode `\{=1 \catcode `\}=2 + \catcode `\$=3 \catcode `\&=4 \catcode `\#=6 + \catcode `\^=7 \catcode `\_=8 \catcode `\~=\active \let~=\tie + \catcode `\%=14 + \catcode `\+=\other + \catcode `\"=\other + \catcode `\|=\other + \catcode `\<=\other + \catcode `\>=\other + \escapechar=`\\ + % + \let\b=\ptexb + \let\bullet=\ptexbullet + \let\c=\ptexc + \let\,=\ptexcomma + \let\.=\ptexdot + \let\dots=\ptexdots + \let\equiv=\ptexequiv + \let\!=\ptexexclam + \let\i=\ptexi + \let\indent=\ptexindent + \let\noindent=\ptexnoindent + \let\{=\ptexlbrace + \let\+=\tabalign + \let\}=\ptexrbrace + \let\/=\ptexslash + \let\*=\ptexstar + \let\t=\ptext + \let\frenchspacing=\plainfrenchspacing + % + \def\endldots{\mathinner{\ldots\ldots\ldots\ldots}}% + \def\enddots{\relax\ifmmode\endldots\else$\mathsurround=0pt \endldots\,$\fi}% + \def\@{@}% +} +% There is no need to define \Etex. + +% Define @lisp ... @end lisp. +% @lisp environment forms a group so it can rebind things, +% including the definition of @end lisp (which normally is erroneous). + +% Amount to narrow the margins by for @lisp. +\newskip\lispnarrowing \lispnarrowing=0.4in + +% This is the definition that ^^M gets inside @lisp, @example, and other +% such environments. \null is better than a space, since it doesn't +% have any width. +\def\lisppar{\null\endgraf} + +% This space is always present above and below environments. +\newskip\envskipamount \envskipamount = 0pt + +% Make spacing and below environment symmetrical. We use \parskip here +% to help in doing that, since in @example-like environments \parskip +% is reset to zero; thus the \afterenvbreak inserts no space -- but the +% start of the next paragraph will insert \parskip. +% +\def\aboveenvbreak{{% + % =10000 instead of <10000 because of a special case in \itemzzz and + % \sectionheading, q.v. + \ifnum \lastpenalty=10000 \else + \advance\envskipamount by \parskip + \endgraf + \ifdim\lastskip<\envskipamount + \removelastskip + % it's not a good place to break if the last penalty was \nobreak + % or better ... + \ifnum\lastpenalty<10000 \penalty-50 \fi + \vskip\envskipamount + \fi + \fi +}} + +\let\afterenvbreak = \aboveenvbreak + +% \nonarrowing is a flag. If "set", @lisp etc don't narrow margins; it will +% also clear it, so that its embedded environments do the narrowing again. +\let\nonarrowing=\relax + +% @cartouche ... @end cartouche: draw rectangle w/rounded corners around +% environment contents. +\font\circle=lcircle10 +\newdimen\circthick +\newdimen\cartouter\newdimen\cartinner +\newskip\normbskip\newskip\normpskip\newskip\normlskip +\circthick=\fontdimen8\circle +% +\def\ctl{{\circle\char'013\hskip -6pt}}% 6pt from pl file: 1/2charwidth +\def\ctr{{\hskip 6pt\circle\char'010}} +\def\cbl{{\circle\char'012\hskip -6pt}} +\def\cbr{{\hskip 6pt\circle\char'011}} +\def\carttop{\hbox to \cartouter{\hskip\lskip + \ctl\leaders\hrule height\circthick\hfil\ctr + \hskip\rskip}} +\def\cartbot{\hbox to \cartouter{\hskip\lskip + \cbl\leaders\hrule height\circthick\hfil\cbr + \hskip\rskip}} +% +\newskip\lskip\newskip\rskip + +\envdef\cartouche{% + \ifhmode\par\fi % can't be in the midst of a paragraph. + \startsavinginserts + \lskip=\leftskip \rskip=\rightskip + \leftskip=0pt\rightskip=0pt % we want these *outside*. + \cartinner=\hsize \advance\cartinner by-\lskip + \advance\cartinner by-\rskip + \cartouter=\hsize + \advance\cartouter by 18.4pt % allow for 3pt kerns on either + % side, and for 6pt waste from + % each corner char, and rule thickness + \normbskip=\baselineskip \normpskip=\parskip \normlskip=\lineskip + % Flag to tell @lisp, etc., not to narrow margin. + \let\nonarrowing = t% + \vbox\bgroup + \baselineskip=0pt\parskip=0pt\lineskip=0pt + \carttop + \hbox\bgroup + \hskip\lskip + \vrule\kern3pt + \vbox\bgroup + \kern3pt + \hsize=\cartinner + \baselineskip=\normbskip + \lineskip=\normlskip + \parskip=\normpskip + \vskip -\parskip + \comment % For explanation, see the end of \def\group. +} +\def\Ecartouche{% + \ifhmode\par\fi + \kern3pt + \egroup + \kern3pt\vrule + \hskip\rskip + \egroup + \cartbot + \egroup + \checkinserts +} + + +% This macro is called at the beginning of all the @example variants, +% inside a group. +\def\nonfillstart{% + \aboveenvbreak + \hfuzz = 12pt % Don't be fussy + \sepspaces % Make spaces be word-separators rather than space tokens. + \let\par = \lisppar % don't ignore blank lines + \obeylines % each line of input is a line of output + \parskip = 0pt + \parindent = 0pt + \emergencystretch = 0pt % don't try to avoid overfull boxes + \ifx\nonarrowing\relax + \advance \leftskip by \lispnarrowing + \exdentamount=\lispnarrowing + \else + \let\nonarrowing = \relax + \fi + \let\exdent=\nofillexdent +} + +% If you want all examples etc. small: @set dispenvsize small. +% If you want even small examples the full size: @set dispenvsize nosmall. +% This affects the following displayed environments: +% @example, @display, @format, @lisp +% +\def\smallword{small} +\def\nosmallword{nosmall} +\let\SETdispenvsize\relax +\def\setnormaldispenv{% + \ifx\SETdispenvsize\smallword + \smallexamplefonts \rm + \fi +} +\def\setsmalldispenv{% + \ifx\SETdispenvsize\nosmallword + \else + \smallexamplefonts \rm + \fi +} + +% We often define two environments, @foo and @smallfoo. +% Let's do it by one command: +\def\makedispenv #1#2{ + \expandafter\envdef\csname#1\endcsname {\setnormaldispenv #2} + \expandafter\envdef\csname small#1\endcsname {\setsmalldispenv #2} + \expandafter\let\csname E#1\endcsname \afterenvbreak + \expandafter\let\csname Esmall#1\endcsname \afterenvbreak +} + +% Define two synonyms: +\def\maketwodispenvs #1#2#3{ + \makedispenv{#1}{#3} + \makedispenv{#2}{#3} +} + +% @lisp: indented, narrowed, typewriter font; @example: same as @lisp. +% +% @smallexample and @smalllisp: use smaller fonts. +% Originally contributed by Pavel@xerox. +% +\maketwodispenvs {lisp}{example}{% + \nonfillstart + \tt\quoteexpand + \let\kbdfont = \kbdexamplefont % Allow @kbd to do something special. + \gobble % eat return +} +% @display/@smalldisplay: same as @lisp except keep current font. +% +\makedispenv {display}{% + \nonfillstart + \gobble +} + +% @format/@smallformat: same as @display except don't narrow margins. +% +\makedispenv{format}{% + \let\nonarrowing = t% + \nonfillstart + \gobble +} + +% @flushleft: same as @format, but doesn't obey \SETdispenvsize. +\envdef\flushleft{% + \let\nonarrowing = t% + \nonfillstart + \gobble +} +\let\Eflushleft = \afterenvbreak + +% @flushright. +% +\envdef\flushright{% + \let\nonarrowing = t% + \nonfillstart + \advance\leftskip by 0pt plus 1fill + \gobble +} +\let\Eflushright = \afterenvbreak + + +% @quotation does normal linebreaking (hence we can't use \nonfillstart) +% and narrows the margins. We keep \parskip nonzero in general, since +% we're doing normal filling. So, when using \aboveenvbreak and +% \afterenvbreak, temporarily make \parskip 0. +% +\envdef\quotation{% + {\parskip=0pt \aboveenvbreak}% because \aboveenvbreak inserts \parskip + \parindent=0pt + % + % @cartouche defines \nonarrowing to inhibit narrowing at next level down. + \ifx\nonarrowing\relax + \advance\leftskip by \lispnarrowing + \advance\rightskip by \lispnarrowing + \exdentamount = \lispnarrowing + \else + \let\nonarrowing = \relax + \fi + \parsearg\quotationlabel +} + +% We have retained a nonzero parskip for the environment, since we're +% doing normal filling. +% +\def\Equotation{% + \par + \ifx\quotationauthor\undefined\else + % indent a bit. + \leftline{\kern 2\leftskip \sl ---\quotationauthor}% + \fi + {\parskip=0pt \afterenvbreak}% +} + +% If we're given an argument, typeset it in bold with a colon after. +\def\quotationlabel#1{% + \def\temp{#1}% + \ifx\temp\empty \else + {\bf #1: }% + \fi +} + + +% LaTeX-like @verbatim...@end verbatim and @verb{...} +% If we want to allow any as delimiter, +% we need the curly braces so that makeinfo sees the @verb command, eg: +% `@verbx...x' would look like the '@verbx' command. --janneke@gnu.org +% +% [Knuth]: Donald Ervin Knuth, 1996. The TeXbook. +% +% [Knuth] p.344; only we need to do the other characters Texinfo sets +% active too. Otherwise, they get lost as the first character on a +% verbatim line. +\def\dospecials{% + \do\ \do\\\do\{\do\}\do\$\do\&% + \do\#\do\^\do\^^K\do\_\do\^^A\do\%\do\~% + \do\<\do\>\do\|\do\@\do+\do\"% +} +% +% [Knuth] p. 380 +\def\uncatcodespecials{% + \def\do##1{\catcode`##1=\other}\dospecials} +% +% [Knuth] pp. 380,381,391 +% Disable Spanish ligatures ?` and !` of \tt font +\begingroup + \catcode`\`=\active\gdef`{\relax\lq} +\endgroup +% +% Setup for the @verb command. +% +% Eight spaces for a tab +\begingroup + \catcode`\^^I=\active + \gdef\tabeightspaces{\catcode`\^^I=\active\def^^I{\ \ \ \ \ \ \ \ }} +\endgroup +% +\def\setupverb{% + \tt % easiest (and conventionally used) font for verbatim + \def\par{\leavevmode\endgraf}% + \catcode`\`=\active + \tabeightspaces + % Respect line breaks, + % print special symbols as themselves, and + % make each space count + % must do in this order: + \obeylines \uncatcodespecials \sepspaces +} + +% Setup for the @verbatim environment +% +% Real tab expansion +\newdimen\tabw \setbox0=\hbox{\tt\space} \tabw=8\wd0 % tab amount +% +\def\starttabbox{\setbox0=\hbox\bgroup} + +% Allow an option to not replace quotes with a regular directed right +% quote/apostrophe (char 0x27), but instead use the undirected quote +% from cmtt (char 0x0d). The undirected quote is ugly, so don't make it +% the default, but it works for pasting with more pdf viewers (at least +% evince), the lilypond developers report. xpdf does work with the +% regular 0x27. +% +\def\codequoteright{% + \expandafter\ifx\csname SETcodequoteundirected\endcsname\relax + '% + \else + \char'15 + \fi +} +% +% and a similar option for the left quote char vs. a grave accent. +% Modern fonts display ASCII 0x60 as a grave accent, so some people like +% the code environments to do likewise. +% +\def\codequoteleft{% + \expandafter\ifx\csname SETcodequotebacktick\endcsname\relax + `% + \else + \char'22 + \fi +} +% +\begingroup + \catcode`\^^I=\active + \gdef\tabexpand{% + \catcode`\^^I=\active + \def^^I{\leavevmode\egroup + \dimen0=\wd0 % the width so far, or since the previous tab + \divide\dimen0 by\tabw + \multiply\dimen0 by\tabw % compute previous multiple of \tabw + \advance\dimen0 by\tabw % advance to next multiple of \tabw + \wd0=\dimen0 \box0 \starttabbox + }% + } + \catcode`\'=\active + \gdef\rquoteexpand{\catcode\rquoteChar=\active \def'{\codequoteright}}% + % + \catcode`\`=\active + \gdef\lquoteexpand{\catcode\lquoteChar=\active \def`{\codequoteleft}}% + % + \gdef\quoteexpand{\rquoteexpand \lquoteexpand}% +\endgroup + +% start the verbatim environment. +\def\setupverbatim{% + \let\nonarrowing = t% + \nonfillstart + % Easiest (and conventionally used) font for verbatim + \tt + \def\par{\leavevmode\egroup\box0\endgraf}% + \catcode`\`=\active + \tabexpand + \quoteexpand + % Respect line breaks, + % print special symbols as themselves, and + % make each space count + % must do in this order: + \obeylines \uncatcodespecials \sepspaces + \everypar{\starttabbox}% +} + +% Do the @verb magic: verbatim text is quoted by unique +% delimiter characters. Before first delimiter expect a +% right brace, after last delimiter expect closing brace: +% +% \def\doverb'{'#1'}'{#1} +% +% [Knuth] p. 382; only eat outer {} +\begingroup + \catcode`[=1\catcode`]=2\catcode`\{=\other\catcode`\}=\other + \gdef\doverb{#1[\def\next##1#1}[##1\endgroup]\next] +\endgroup +% +\def\verb{\begingroup\setupverb\doverb} +% +% +% Do the @verbatim magic: define the macro \doverbatim so that +% the (first) argument ends when '@end verbatim' is reached, ie: +% +% \def\doverbatim#1@end verbatim{#1} +% +% For Texinfo it's a lot easier than for LaTeX, +% because texinfo's \verbatim doesn't stop at '\end{verbatim}': +% we need not redefine '\', '{' and '}'. +% +% Inspired by LaTeX's verbatim command set [latex.ltx] +% +\begingroup + \catcode`\ =\active + \obeylines % + % ignore everything up to the first ^^M, that's the newline at the end + % of the @verbatim input line itself. Otherwise we get an extra blank + % line in the output. + \xdef\doverbatim#1^^M#2@end verbatim{#2\noexpand\end\gobble verbatim}% + % We really want {...\end verbatim} in the body of the macro, but + % without the active space; thus we have to use \xdef and \gobble. +\endgroup +% +\envdef\verbatim{% + \setupverbatim\doverbatim +} +\let\Everbatim = \afterenvbreak + + +% @verbatiminclude FILE - insert text of file in verbatim environment. +% +\def\verbatiminclude{\parseargusing\filenamecatcodes\doverbatiminclude} +% +\def\doverbatiminclude#1{% + {% + \makevalueexpandable + \setupverbatim + \input #1 + \afterenvbreak + }% +} + +% @copying ... @end copying. +% Save the text away for @insertcopying later. +% +% We save the uninterpreted tokens, rather than creating a box. +% Saving the text in a box would be much easier, but then all the +% typesetting commands (@smallbook, font changes, etc.) have to be done +% beforehand -- and a) we want @copying to be done first in the source +% file; b) letting users define the frontmatter in as flexible order as +% possible is very desirable. +% +\def\copying{\checkenv{}\begingroup\scanargctxt\docopying} +\def\docopying#1@end copying{\endgroup\def\copyingtext{#1}} +% +\def\insertcopying{% + \begingroup + \parindent = 0pt % paragraph indentation looks wrong on title page + \scanexp\copyingtext + \endgroup +} + +\message{defuns,} +% @defun etc. + +\newskip\defbodyindent \defbodyindent=.4in +\newskip\defargsindent \defargsindent=50pt +\newskip\deflastargmargin \deflastargmargin=18pt + +% Start the processing of @deffn: +\def\startdefun{% + \ifnum\lastpenalty<10000 + \medbreak + \else + % If there are two @def commands in a row, we'll have a \nobreak, + % which is there to keep the function description together with its + % header. But if there's nothing but headers, we need to allow a + % break somewhere. Check specifically for penalty 10002, inserted + % by \defargscommonending, instead of 10000, since the sectioning + % commands also insert a nobreak penalty, and we don't want to allow + % a break between a section heading and a defun. + % + \ifnum\lastpenalty=10002 \penalty2000 \fi + % + % Similarly, after a section heading, do not allow a break. + % But do insert the glue. + \medskip % preceded by discardable penalty, so not a breakpoint + \fi + % + \parindent=0in + \advance\leftskip by \defbodyindent + \exdentamount=\defbodyindent +} + +\def\dodefunx#1{% + % First, check whether we are in the right environment: + \checkenv#1% + % + % As above, allow line break if we have multiple x headers in a row. + % It's not a great place, though. + \ifnum\lastpenalty=10002 \penalty3000 \fi + % + % And now, it's time to reuse the body of the original defun: + \expandafter\gobbledefun#1% +} +\def\gobbledefun#1\startdefun{} + +% \printdefunline \deffnheader{text} +% +\def\printdefunline#1#2{% + \begingroup + % call \deffnheader: + #1#2 \endheader + % common ending: + \interlinepenalty = 10000 + \advance\rightskip by 0pt plus 1fil + \endgraf + \nobreak\vskip -\parskip + \penalty 10002 % signal to \startdefun and \dodefunx + % Some of the @defun-type tags do not enable magic parentheses, + % rendering the following check redundant. But we don't optimize. + \checkparencounts + \endgroup +} + +\def\Edefun{\endgraf\medbreak} + +% \makedefun{deffn} creates \deffn, \deffnx and \Edeffn; +% the only thing remainnig is to define \deffnheader. +% +\def\makedefun#1{% + \expandafter\let\csname E#1\endcsname = \Edefun + \edef\temp{\noexpand\domakedefun + \makecsname{#1}\makecsname{#1x}\makecsname{#1header}}% + \temp +} + +% \domakedefun \deffn \deffnx \deffnheader +% +% Define \deffn and \deffnx, without parameters. +% \deffnheader has to be defined explicitly. +% +\def\domakedefun#1#2#3{% + \envdef#1{% + \startdefun + \parseargusing\activeparens{\printdefunline#3}% + }% + \def#2{\dodefunx#1}% + \def#3% +} + +%%% Untyped functions: + +% @deffn category name args +\makedefun{deffn}{\deffngeneral{}} + +% @deffn category class name args +\makedefun{defop}#1 {\defopon{#1\ \putwordon}} + +% \defopon {category on}class name args +\def\defopon#1#2 {\deffngeneral{\putwordon\ \code{#2}}{#1\ \code{#2}} } + +% \deffngeneral {subind}category name args +% +\def\deffngeneral#1#2 #3 #4\endheader{% + % Remember that \dosubind{fn}{foo}{} is equivalent to \doind{fn}{foo}. + \dosubind{fn}{\code{#3}}{#1}% + \defname{#2}{}{#3}\magicamp\defunargs{#4\unskip}% +} + +%%% Typed functions: + +% @deftypefn category type name args +\makedefun{deftypefn}{\deftypefngeneral{}} + +% @deftypeop category class type name args +\makedefun{deftypeop}#1 {\deftypeopon{#1\ \putwordon}} + +% \deftypeopon {category on}class type name args +\def\deftypeopon#1#2 {\deftypefngeneral{\putwordon\ \code{#2}}{#1\ \code{#2}} } + +% \deftypefngeneral {subind}category type name args +% +\def\deftypefngeneral#1#2 #3 #4 #5\endheader{% + \dosubind{fn}{\code{#4}}{#1}% + \defname{#2}{#3}{#4}\defunargs{#5\unskip}% +} + +%%% Typed variables: + +% @deftypevr category type var args +\makedefun{deftypevr}{\deftypecvgeneral{}} + +% @deftypecv category class type var args +\makedefun{deftypecv}#1 {\deftypecvof{#1\ \putwordof}} + +% \deftypecvof {category of}class type var args +\def\deftypecvof#1#2 {\deftypecvgeneral{\putwordof\ \code{#2}}{#1\ \code{#2}} } + +% \deftypecvgeneral {subind}category type var args +% +\def\deftypecvgeneral#1#2 #3 #4 #5\endheader{% + \dosubind{vr}{\code{#4}}{#1}% + \defname{#2}{#3}{#4}\defunargs{#5\unskip}% +} + +%%% Untyped variables: + +% @defvr category var args +\makedefun{defvr}#1 {\deftypevrheader{#1} {} } + +% @defcv category class var args +\makedefun{defcv}#1 {\defcvof{#1\ \putwordof}} + +% \defcvof {category of}class var args +\def\defcvof#1#2 {\deftypecvof{#1}#2 {} } + +%%% Type: +% @deftp category name args +\makedefun{deftp}#1 #2 #3\endheader{% + \doind{tp}{\code{#2}}% + \defname{#1}{}{#2}\defunargs{#3\unskip}% +} + +% Remaining @defun-like shortcuts: +\makedefun{defun}{\deffnheader{\putwordDeffunc} } +\makedefun{defmac}{\deffnheader{\putwordDefmac} } +\makedefun{defspec}{\deffnheader{\putwordDefspec} } +\makedefun{deftypefun}{\deftypefnheader{\putwordDeffunc} } +\makedefun{defvar}{\defvrheader{\putwordDefvar} } +\makedefun{defopt}{\defvrheader{\putwordDefopt} } +\makedefun{deftypevar}{\deftypevrheader{\putwordDefvar} } +\makedefun{defmethod}{\defopon\putwordMethodon} +\makedefun{deftypemethod}{\deftypeopon\putwordMethodon} +\makedefun{defivar}{\defcvof\putwordInstanceVariableof} +\makedefun{deftypeivar}{\deftypecvof\putwordInstanceVariableof} + +% \defname, which formats the name of the @def (not the args). +% #1 is the category, such as "Function". +% #2 is the return type, if any. +% #3 is the function name. +% +% We are followed by (but not passed) the arguments, if any. +% +\def\defname#1#2#3{% + % Get the values of \leftskip and \rightskip as they were outside the @def... + \advance\leftskip by -\defbodyindent + % + % How we'll format the type name. Putting it in brackets helps + % distinguish it from the body text that may end up on the next line + % just below it. + \def\temp{#1}% + \setbox0=\hbox{\kern\deflastargmargin \ifx\temp\empty\else [\rm\temp]\fi} + % + % Figure out line sizes for the paragraph shape. + % The first line needs space for \box0; but if \rightskip is nonzero, + % we need only space for the part of \box0 which exceeds it: + \dimen0=\hsize \advance\dimen0 by -\wd0 \advance\dimen0 by \rightskip + % The continuations: + \dimen2=\hsize \advance\dimen2 by -\defargsindent + % (plain.tex says that \dimen1 should be used only as global.) + \parshape 2 0in \dimen0 \defargsindent \dimen2 + % + % Put the type name to the right margin. + \noindent + \hbox to 0pt{% + \hfil\box0 \kern-\hsize + % \hsize has to be shortened this way: + \kern\leftskip + % Intentionally do not respect \rightskip, since we need the space. + }% + % + % Allow all lines to be underfull without complaint: + \tolerance=10000 \hbadness=10000 + \exdentamount=\defbodyindent + {% + % defun fonts. We use typewriter by default (used to be bold) because: + % . we're printing identifiers, they should be in tt in principle. + % . in languages with many accents, such as Czech or French, it's + % common to leave accents off identifiers. The result looks ok in + % tt, but exceedingly strange in rm. + % . we don't want -- and --- to be treated as ligatures. + % . this still does not fix the ?` and !` ligatures, but so far no + % one has made identifiers using them :). + \df \tt + \def\temp{#2}% return value type + \ifx\temp\empty\else \tclose{\temp} \fi + #3% output function name + }% + {\rm\enskip}% hskip 0.5 em of \tenrm + % + \boldbrax + % arguments will be output next, if any. +} + +% Print arguments in slanted roman (not ttsl), inconsistently with using +% tt for the name. This is because literal text is sometimes needed in +% the argument list (groff manual), and ttsl and tt are not very +% distinguishable. Prevent hyphenation at `-' chars. +% +\def\defunargs#1{% + % use sl by default (not ttsl), + % tt for the names. + \df \sl \hyphenchar\font=0 + % + % On the other hand, if an argument has two dashes (for instance), we + % want a way to get ttsl. Let's try @var for that. + \let\var=\ttslanted + #1% + \sl\hyphenchar\font=45 +} + +% We want ()&[] to print specially on the defun line. +% +\def\activeparens{% + \catcode`\(=\active \catcode`\)=\active + \catcode`\[=\active \catcode`\]=\active + \catcode`\&=\active +} + +% Make control sequences which act like normal parenthesis chars. +\let\lparen = ( \let\rparen = ) + +% Be sure that we always have a definition for `(', etc. For example, +% if the fn name has parens in it, \boldbrax will not be in effect yet, +% so TeX would otherwise complain about undefined control sequence. +{ + \activeparens + \global\let(=\lparen \global\let)=\rparen + \global\let[=\lbrack \global\let]=\rbrack + \global\let& = \& + + \gdef\boldbrax{\let(=\opnr\let)=\clnr\let[=\lbrb\let]=\rbrb} + \gdef\magicamp{\let&=\amprm} +} + +\newcount\parencount + +% If we encounter &foo, then turn on ()-hacking afterwards +\newif\ifampseen +\def\amprm#1 {\ampseentrue{\bf\ }} + +\def\parenfont{% + \ifampseen + % At the first level, print parens in roman, + % otherwise use the default font. + \ifnum \parencount=1 \rm \fi + \else + % The \sf parens (in \boldbrax) actually are a little bolder than + % the contained text. This is especially needed for [ and ] . + \sf + \fi +} +\def\infirstlevel#1{% + \ifampseen + \ifnum\parencount=1 + #1% + \fi + \fi +} +\def\bfafterword#1 {#1 \bf} + +\def\opnr{% + \global\advance\parencount by 1 + {\parenfont(}% + \infirstlevel \bfafterword +} +\def\clnr{% + {\parenfont)}% + \infirstlevel \sl + \global\advance\parencount by -1 +} + +\newcount\brackcount +\def\lbrb{% + \global\advance\brackcount by 1 + {\bf[}% +} +\def\rbrb{% + {\bf]}% + \global\advance\brackcount by -1 +} + +\def\checkparencounts{% + \ifnum\parencount=0 \else \badparencount \fi + \ifnum\brackcount=0 \else \badbrackcount \fi +} +\def\badparencount{% + \errmessage{Unbalanced parentheses in @def}% + \global\parencount=0 +} +\def\badbrackcount{% + \errmessage{Unbalanced square braces in @def}% + \global\brackcount=0 +} + + +\message{macros,} +% @macro. + +% To do this right we need a feature of e-TeX, \scantokens, +% which we arrange to emulate with a temporary file in ordinary TeX. +\ifx\eTeXversion\undefined + \newwrite\macscribble + \def\scantokens#1{% + \toks0={#1}% + \immediate\openout\macscribble=\jobname.tmp + \immediate\write\macscribble{\the\toks0}% + \immediate\closeout\macscribble + \input \jobname.tmp + } +\fi + +\def\scanmacro#1{% + \begingroup + \newlinechar`\^^M + \let\xeatspaces\eatspaces + % Undo catcode changes of \startcontents and \doprintindex + % When called from @insertcopying or (short)caption, we need active + % backslash to get it printed correctly. Previously, we had + % \catcode`\\=\other instead. We'll see whether a problem appears + % with macro expansion. --kasal, 19aug04 + \catcode`\@=0 \catcode`\\=\active \escapechar=`\@ + % ... and \example + \spaceisspace + % + % Append \endinput to make sure that TeX does not see the ending newline. + % I've verified that it is necessary both for e-TeX and for ordinary TeX + % --kasal, 29nov03 + \scantokens{#1\endinput}% + \endgroup +} + +\def\scanexp#1{% + \edef\temp{\noexpand\scanmacro{#1}}% + \temp +} + +\newcount\paramno % Count of parameters +\newtoks\macname % Macro name +\newif\ifrecursive % Is it recursive? + +% List of all defined macros in the form +% \definedummyword\macro1\definedummyword\macro2... +% Currently is also contains all @aliases; the list can be split +% if there is a need. +\def\macrolist{} + +% Add the macro to \macrolist +\def\addtomacrolist#1{\expandafter \addtomacrolistxxx \csname#1\endcsname} +\def\addtomacrolistxxx#1{% + \toks0 = \expandafter{\macrolist\definedummyword#1}% + \xdef\macrolist{\the\toks0}% +} + +% Utility routines. +% This does \let #1 = #2, with \csnames; that is, +% \let \csname#1\endcsname = \csname#2\endcsname +% (except of course we have to play expansion games). +% +\def\cslet#1#2{% + \expandafter\let + \csname#1\expandafter\endcsname + \csname#2\endcsname +} + +% Trim leading and trailing spaces off a string. +% Concepts from aro-bend problem 15 (see CTAN). +{\catcode`\@=11 +\gdef\eatspaces #1{\expandafter\trim@\expandafter{#1 }} +\gdef\trim@ #1{\trim@@ @#1 @ #1 @ @@} +\gdef\trim@@ #1@ #2@ #3@@{\trim@@@\empty #2 @} +\def\unbrace#1{#1} +\unbrace{\gdef\trim@@@ #1 } #2@{#1} +} + +% Trim a single trailing ^^M off a string. +{\catcode`\^^M=\other \catcode`\Q=3% +\gdef\eatcr #1{\eatcra #1Q^^MQ}% +\gdef\eatcra#1^^MQ{\eatcrb#1Q}% +\gdef\eatcrb#1Q#2Q{#1}% +} + +% Macro bodies are absorbed as an argument in a context where +% all characters are catcode 10, 11 or 12, except \ which is active +% (as in normal texinfo). It is necessary to change the definition of \. + +% It's necessary to have hard CRs when the macro is executed. This is +% done by making ^^M (\endlinechar) catcode 12 when reading the macro +% body, and then making it the \newlinechar in \scanmacro. + +\def\scanctxt{% + \catcode`\"=\other + \catcode`\+=\other + \catcode`\<=\other + \catcode`\>=\other + \catcode`\@=\other + \catcode`\^=\other + \catcode`\_=\other + \catcode`\|=\other + \catcode`\~=\other +} + +\def\scanargctxt{% + \scanctxt + \catcode`\\=\other + \catcode`\^^M=\other +} + +\def\macrobodyctxt{% + \scanctxt + \catcode`\{=\other + \catcode`\}=\other + \catcode`\^^M=\other + \usembodybackslash +} + +\def\macroargctxt{% + \scanctxt + \catcode`\\=\other +} + +% \mbodybackslash is the definition of \ in @macro bodies. +% It maps \foo\ => \csname macarg.foo\endcsname => #N +% where N is the macro parameter number. +% We define \csname macarg.\endcsname to be \realbackslash, so +% \\ in macro replacement text gets you a backslash. + +{\catcode`@=0 @catcode`@\=@active + @gdef@usembodybackslash{@let\=@mbodybackslash} + @gdef@mbodybackslash#1\{@csname macarg.#1@endcsname} +} +\expandafter\def\csname macarg.\endcsname{\realbackslash} + +\def\macro{\recursivefalse\parsearg\macroxxx} +\def\rmacro{\recursivetrue\parsearg\macroxxx} + +\def\macroxxx#1{% + \getargs{#1}% now \macname is the macname and \argl the arglist + \ifx\argl\empty % no arguments + \paramno=0% + \else + \expandafter\parsemargdef \argl;% + \fi + \if1\csname ismacro.\the\macname\endcsname + \message{Warning: redefining \the\macname}% + \else + \expandafter\ifx\csname \the\macname\endcsname \relax + \else \errmessage{Macro name \the\macname\space already defined}\fi + \global\cslet{macsave.\the\macname}{\the\macname}% + \global\expandafter\let\csname ismacro.\the\macname\endcsname=1% + \addtomacrolist{\the\macname}% + \fi + \begingroup \macrobodyctxt + \ifrecursive \expandafter\parsermacbody + \else \expandafter\parsemacbody + \fi} + +\parseargdef\unmacro{% + \if1\csname ismacro.#1\endcsname + \global\cslet{#1}{macsave.#1}% + \global\expandafter\let \csname ismacro.#1\endcsname=0% + % Remove the macro name from \macrolist: + \begingroup + \expandafter\let\csname#1\endcsname \relax + \let\definedummyword\unmacrodo + \xdef\macrolist{\macrolist}% + \endgroup + \else + \errmessage{Macro #1 not defined}% + \fi +} + +% Called by \do from \dounmacro on each macro. The idea is to omit any +% macro definitions that have been changed to \relax. +% +\def\unmacrodo#1{% + \ifx #1\relax + % remove this + \else + \noexpand\definedummyword \noexpand#1% + \fi +} + +% This makes use of the obscure feature that if the last token of a +% is #, then the preceding argument is delimited by +% an opening brace, and that opening brace is not consumed. +\def\getargs#1{\getargsxxx#1{}} +\def\getargsxxx#1#{\getmacname #1 \relax\getmacargs} +\def\getmacname #1 #2\relax{\macname={#1}} +\def\getmacargs#1{\def\argl{#1}} + +% Parse the optional {params} list. Set up \paramno and \paramlist +% so \defmacro knows what to do. Define \macarg.blah for each blah +% in the params list, to be ##N where N is the position in that list. +% That gets used by \mbodybackslash (above). + +% We need to get `macro parameter char #' into several definitions. +% The technique used is stolen from LaTeX: let \hash be something +% unexpandable, insert that wherever you need a #, and then redefine +% it to # just before using the token list produced. +% +% The same technique is used to protect \eatspaces till just before +% the macro is used. + +\def\parsemargdef#1;{\paramno=0\def\paramlist{}% + \let\hash\relax\let\xeatspaces\relax\parsemargdefxxx#1,;,} +\def\parsemargdefxxx#1,{% + \if#1;\let\next=\relax + \else \let\next=\parsemargdefxxx + \advance\paramno by 1% + \expandafter\edef\csname macarg.\eatspaces{#1}\endcsname + {\xeatspaces{\hash\the\paramno}}% + \edef\paramlist{\paramlist\hash\the\paramno,}% + \fi\next} + +% These two commands read recursive and nonrecursive macro bodies. +% (They're different since rec and nonrec macros end differently.) + +\long\def\parsemacbody#1@end macro% +{\xdef\temp{\eatcr{#1}}\endgroup\defmacro}% +\long\def\parsermacbody#1@end rmacro% +{\xdef\temp{\eatcr{#1}}\endgroup\defmacro}% + +% This defines the macro itself. There are six cases: recursive and +% nonrecursive macros of zero, one, and many arguments. +% Much magic with \expandafter here. +% \xdef is used so that macro definitions will survive the file +% they're defined in; @include reads the file inside a group. +\def\defmacro{% + \let\hash=##% convert placeholders to macro parameter chars + \ifrecursive + \ifcase\paramno + % 0 + \expandafter\xdef\csname\the\macname\endcsname{% + \noexpand\scanmacro{\temp}}% + \or % 1 + \expandafter\xdef\csname\the\macname\endcsname{% + \bgroup\noexpand\macroargctxt + \noexpand\braceorline + \expandafter\noexpand\csname\the\macname xxx\endcsname}% + \expandafter\xdef\csname\the\macname xxx\endcsname##1{% + \egroup\noexpand\scanmacro{\temp}}% + \else % many + \expandafter\xdef\csname\the\macname\endcsname{% + \bgroup\noexpand\macroargctxt + \noexpand\csname\the\macname xx\endcsname}% + \expandafter\xdef\csname\the\macname xx\endcsname##1{% + \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% + \expandafter\expandafter + \expandafter\xdef + \expandafter\expandafter + \csname\the\macname xxx\endcsname + \paramlist{\egroup\noexpand\scanmacro{\temp}}% + \fi + \else + \ifcase\paramno + % 0 + \expandafter\xdef\csname\the\macname\endcsname{% + \noexpand\norecurse{\the\macname}% + \noexpand\scanmacro{\temp}\egroup}% + \or % 1 + \expandafter\xdef\csname\the\macname\endcsname{% + \bgroup\noexpand\macroargctxt + \noexpand\braceorline + \expandafter\noexpand\csname\the\macname xxx\endcsname}% + \expandafter\xdef\csname\the\macname xxx\endcsname##1{% + \egroup + \noexpand\norecurse{\the\macname}% + \noexpand\scanmacro{\temp}\egroup}% + \else % many + \expandafter\xdef\csname\the\macname\endcsname{% + \bgroup\noexpand\macroargctxt + \expandafter\noexpand\csname\the\macname xx\endcsname}% + \expandafter\xdef\csname\the\macname xx\endcsname##1{% + \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% + \expandafter\expandafter + \expandafter\xdef + \expandafter\expandafter + \csname\the\macname xxx\endcsname + \paramlist{% + \egroup + \noexpand\norecurse{\the\macname}% + \noexpand\scanmacro{\temp}\egroup}% + \fi + \fi} + +\def\norecurse#1{\bgroup\cslet{#1}{macsave.#1}} + +% \braceorline decides whether the next nonwhitespace character is a +% {. If so it reads up to the closing }, if not, it reads the whole +% line. Whatever was read is then fed to the next control sequence +% as an argument (by \parsebrace or \parsearg) +\def\braceorline#1{\let\macnamexxx=#1\futurelet\nchar\braceorlinexxx} +\def\braceorlinexxx{% + \ifx\nchar\bgroup\else + \expandafter\parsearg + \fi \macnamexxx} + + +% @alias. +% We need some trickery to remove the optional spaces around the equal +% sign. Just make them active and then expand them all to nothing. +\def\alias{\parseargusing\obeyspaces\aliasxxx} +\def\aliasxxx #1{\aliasyyy#1\relax} +\def\aliasyyy #1=#2\relax{% + {% + \expandafter\let\obeyedspace=\empty + \addtomacrolist{#1}% + \xdef\next{\global\let\makecsname{#1}=\makecsname{#2}}% + }% + \next +} + + +\message{cross references,} + +\newwrite\auxfile + +\newif\ifhavexrefs % True if xref values are known. +\newif\ifwarnedxrefs % True if we warned once that they aren't known. + +% @inforef is relatively simple. +\def\inforef #1{\inforefzzz #1,,,,**} +\def\inforefzzz #1,#2,#3,#4**{\putwordSee{} \putwordInfo{} \putwordfile{} \file{\ignorespaces #3{}}, + node \samp{\ignorespaces#1{}}} + +% @node's only job in TeX is to define \lastnode, which is used in +% cross-references. The @node line might or might not have commas, and +% might or might not have spaces before the first comma, like: +% @node foo , bar , ... +% We don't want such trailing spaces in the node name. +% +\parseargdef\node{\checkenv{}\donode #1 ,\finishnodeparse} +% +% also remove a trailing comma, in case of something like this: +% @node Help-Cross, , , Cross-refs +\def\donode#1 ,#2\finishnodeparse{\dodonode #1,\finishnodeparse} +\def\dodonode#1,#2\finishnodeparse{\gdef\lastnode{#1}} + +\let\nwnode=\node +\let\lastnode=\empty + +% Write a cross-reference definition for the current node. #1 is the +% type (Ynumbered, Yappendix, Ynothing). +% +\def\donoderef#1{% + \ifx\lastnode\empty\else + \setref{\lastnode}{#1}% + \global\let\lastnode=\empty + \fi +} + +% @anchor{NAME} -- define xref target at arbitrary point. +% +\newcount\savesfregister +% +\def\savesf{\relax \ifhmode \savesfregister=\spacefactor \fi} +\def\restoresf{\relax \ifhmode \spacefactor=\savesfregister \fi} +\def\anchor#1{\savesf \setref{#1}{Ynothing}\restoresf \ignorespaces} + +% \setref{NAME}{SNT} defines a cross-reference point NAME (a node or an +% anchor), which consists of three parts: +% 1) NAME-title - the current sectioning name taken from \thissection, +% or the anchor name. +% 2) NAME-snt - section number and type, passed as the SNT arg, or +% empty for anchors. +% 3) NAME-pg - the page number. +% +% This is called from \donoderef, \anchor, and \dofloat. In the case of +% floats, there is an additional part, which is not written here: +% 4) NAME-lof - the text as it should appear in a @listoffloats. +% +\def\setref#1#2{% + \pdfmkdest{#1}% + \iflinks + {% + \atdummies % preserve commands, but don't expand them + \edef\writexrdef##1##2{% + \write\auxfile{@xrdef{#1-% #1 of \setref, expanded by the \edef + ##1}{##2}}% these are parameters of \writexrdef + }% + \toks0 = \expandafter{\thissection}% + \immediate \writexrdef{title}{\the\toks0 }% + \immediate \writexrdef{snt}{\csname #2\endcsname}% \Ynumbered etc. + \writexrdef{pg}{\folio}% will be written later, during \shipout + }% + \fi +} + +% @xref, @pxref, and @ref generate cross-references. For \xrefX, #1 is +% the node name, #2 the name of the Info cross-reference, #3 the printed +% node name, #4 the name of the Info file, #5 the name of the printed +% manual. All but the node name can be omitted. +% +\def\pxref#1{\putwordsee{} \xrefX[#1,,,,,,,]} +\def\xref#1{\putwordSee{} \xrefX[#1,,,,,,,]} +\def\ref#1{\xrefX[#1,,,,,,,]} +\def\xrefX[#1,#2,#3,#4,#5,#6]{\begingroup + \unsepspaces + \def\printedmanual{\ignorespaces #5}% + \def\printedrefname{\ignorespaces #3}% + \setbox1=\hbox{\printedmanual\unskip}% + \setbox0=\hbox{\printedrefname\unskip}% + \ifdim \wd0 = 0pt + % No printed node name was explicitly given. + \expandafter\ifx\csname SETxref-automatic-section-title\endcsname\relax + % Use the node name inside the square brackets. + \def\printedrefname{\ignorespaces #1}% + \else + % Use the actual chapter/section title appear inside + % the square brackets. Use the real section title if we have it. + \ifdim \wd1 > 0pt + % It is in another manual, so we don't have it. + \def\printedrefname{\ignorespaces #1}% + \else + \ifhavexrefs + % We know the real title if we have the xref values. + \def\printedrefname{\refx{#1-title}{}}% + \else + % Otherwise just copy the Info node name. + \def\printedrefname{\ignorespaces #1}% + \fi% + \fi + \fi + \fi + % + % Make link in pdf output. + \ifpdf + \leavevmode + \getfilename{#4}% + {\turnoffactive + % See comments at \activebackslashdouble. + {\activebackslashdouble \xdef\pdfxrefdest{#1}% + \backslashparens\pdfxrefdest}% + % + \ifnum\filenamelength>0 + \startlink attr{/Border [0 0 0]}% + goto file{\the\filename.pdf} name{\pdfxrefdest}% + \else + \startlink attr{/Border [0 0 0]}% + goto name{\pdfmkpgn{\pdfxrefdest}}% + \fi + }% + \linkcolor + \fi + % + % Float references are printed completely differently: "Figure 1.2" + % instead of "[somenode], p.3". We distinguish them by the + % LABEL-title being set to a magic string. + {% + % Have to otherify everything special to allow the \csname to + % include an _ in the xref name, etc. + \indexnofonts + \turnoffactive + \expandafter\global\expandafter\let\expandafter\Xthisreftitle + \csname XR#1-title\endcsname + }% + \iffloat\Xthisreftitle + % If the user specified the print name (third arg) to the ref, + % print it instead of our usual "Figure 1.2". + \ifdim\wd0 = 0pt + \refx{#1-snt}{}% + \else + \printedrefname + \fi + % + % if the user also gave the printed manual name (fifth arg), append + % "in MANUALNAME". + \ifdim \wd1 > 0pt + \space \putwordin{} \cite{\printedmanual}% + \fi + \else + % node/anchor (non-float) references. + % + % If we use \unhbox0 and \unhbox1 to print the node names, TeX does not + % insert empty discretionaries after hyphens, which means that it will + % not find a line break at a hyphen in a node names. Since some manuals + % are best written with fairly long node names, containing hyphens, this + % is a loss. Therefore, we give the text of the node name again, so it + % is as if TeX is seeing it for the first time. + \ifdim \wd1 > 0pt + \putwordsection{} ``\printedrefname'' \putwordin{} \cite{\printedmanual}% + \else + % _ (for example) has to be the character _ for the purposes of the + % control sequence corresponding to the node, but it has to expand + % into the usual \leavevmode...\vrule stuff for purposes of + % printing. So we \turnoffactive for the \refx-snt, back on for the + % printing, back off for the \refx-pg. + {\turnoffactive + % Only output a following space if the -snt ref is nonempty; for + % @unnumbered and @anchor, it won't be. + \setbox2 = \hbox{\ignorespaces \refx{#1-snt}{}}% + \ifdim \wd2 > 0pt \refx{#1-snt}\space\fi + }% + % output the `[mynode]' via a macro so it can be overridden. + \xrefprintnodename\printedrefname + % + % But we always want a comma and a space: + ,\space + % + % output the `page 3'. + \turnoffactive \putwordpage\tie\refx{#1-pg}{}% + \fi + \fi + \endlink +\endgroup} + +% This macro is called from \xrefX for the `[nodename]' part of xref +% output. It's a separate macro only so it can be changed more easily, +% since square brackets don't work well in some documents. Particularly +% one that Bob is working on :). +% +\def\xrefprintnodename#1{[#1]} + +% Things referred to by \setref. +% +\def\Ynothing{} +\def\Yomitfromtoc{} +\def\Ynumbered{% + \ifnum\secno=0 + \putwordChapter@tie \the\chapno + \else \ifnum\subsecno=0 + \putwordSection@tie \the\chapno.\the\secno + \else \ifnum\subsubsecno=0 + \putwordSection@tie \the\chapno.\the\secno.\the\subsecno + \else + \putwordSection@tie \the\chapno.\the\secno.\the\subsecno.\the\subsubsecno + \fi\fi\fi +} +\def\Yappendix{% + \ifnum\secno=0 + \putwordAppendix@tie @char\the\appendixno{}% + \else \ifnum\subsecno=0 + \putwordSection@tie @char\the\appendixno.\the\secno + \else \ifnum\subsubsecno=0 + \putwordSection@tie @char\the\appendixno.\the\secno.\the\subsecno + \else + \putwordSection@tie + @char\the\appendixno.\the\secno.\the\subsecno.\the\subsubsecno + \fi\fi\fi +} + +% Define \refx{NAME}{SUFFIX} to reference a cross-reference string named NAME. +% If its value is nonempty, SUFFIX is output afterward. +% +\def\refx#1#2{% + {% + \indexnofonts + \otherbackslash + \expandafter\global\expandafter\let\expandafter\thisrefX + \csname XR#1\endcsname + }% + \ifx\thisrefX\relax + % If not defined, say something at least. + \angleleft un\-de\-fined\angleright + \iflinks + \ifhavexrefs + \message{\linenumber Undefined cross reference `#1'.}% + \else + \ifwarnedxrefs\else + \global\warnedxrefstrue + \message{Cross reference values unknown; you must run TeX again.}% + \fi + \fi + \fi + \else + % It's defined, so just use it. + \thisrefX + \fi + #2% Output the suffix in any case. +} + +% This is the macro invoked by entries in the aux file. Usually it's +% just a \def (we prepend XR to the control sequence name to avoid +% collisions). But if this is a float type, we have more work to do. +% +\def\xrdef#1#2{% + \expandafter\gdef\csname XR#1\endcsname{#2}% remember this xref value. + % + % Was that xref control sequence that we just defined for a float? + \expandafter\iffloat\csname XR#1\endcsname + % it was a float, and we have the (safe) float type in \iffloattype. + \expandafter\let\expandafter\floatlist + \csname floatlist\iffloattype\endcsname + % + % Is this the first time we've seen this float type? + \expandafter\ifx\floatlist\relax + \toks0 = {\do}% yes, so just \do + \else + % had it before, so preserve previous elements in list. + \toks0 = \expandafter{\floatlist\do}% + \fi + % + % Remember this xref in the control sequence \floatlistFLOATTYPE, + % for later use in \listoffloats. + \expandafter\xdef\csname floatlist\iffloattype\endcsname{\the\toks0{#1}}% + \fi +} + +% Read the last existing aux file, if any. No error if none exists. +% +\def\tryauxfile{% + \openin 1 \jobname.aux + \ifeof 1 \else + \readdatafile{aux}% + \global\havexrefstrue + \fi + \closein 1 +} + +\def\setupdatafile{% + \catcode`\^^@=\other + \catcode`\^^A=\other + \catcode`\^^B=\other + \catcode`\^^C=\other + \catcode`\^^D=\other + \catcode`\^^E=\other + \catcode`\^^F=\other + \catcode`\^^G=\other + \catcode`\^^H=\other + \catcode`\^^K=\other + \catcode`\^^L=\other + \catcode`\^^N=\other + \catcode`\^^P=\other + \catcode`\^^Q=\other + \catcode`\^^R=\other + \catcode`\^^S=\other + \catcode`\^^T=\other + \catcode`\^^U=\other + \catcode`\^^V=\other + \catcode`\^^W=\other + \catcode`\^^X=\other + \catcode`\^^Z=\other + \catcode`\^^[=\other + \catcode`\^^\=\other + \catcode`\^^]=\other + \catcode`\^^^=\other + \catcode`\^^_=\other + % It was suggested to set the catcode of ^ to 7, which would allow ^^e4 etc. + % in xref tags, i.e., node names. But since ^^e4 notation isn't + % supported in the main text, it doesn't seem desirable. Furthermore, + % that is not enough: for node names that actually contain a ^ + % character, we would end up writing a line like this: 'xrdef {'hat + % b-title}{'hat b} and \xrdef does a \csname...\endcsname on the first + % argument, and \hat is not an expandable control sequence. It could + % all be worked out, but why? Either we support ^^ or we don't. + % + % The other change necessary for this was to define \auxhat: + % \def\auxhat{\def^{'hat }}% extra space so ok if followed by letter + % and then to call \auxhat in \setq. + % + \catcode`\^=\other + % + % Special characters. Should be turned off anyway, but... + \catcode`\~=\other + \catcode`\[=\other + \catcode`\]=\other + \catcode`\"=\other + \catcode`\_=\other + \catcode`\|=\other + \catcode`\<=\other + \catcode`\>=\other + \catcode`\$=\other + \catcode`\#=\other + \catcode`\&=\other + \catcode`\%=\other + \catcode`+=\other % avoid \+ for paranoia even though we've turned it off + % + % This is to support \ in node names and titles, since the \ + % characters end up in a \csname. It's easier than + % leaving it active and making its active definition an actual \ + % character. What I don't understand is why it works in the *value* + % of the xrdef. Seems like it should be a catcode12 \, and that + % should not typeset properly. But it works, so I'm moving on for + % now. --karl, 15jan04. + \catcode`\\=\other + % + % Make the characters 128-255 be printing characters. + {% + \count1=128 + \def\loop{% + \catcode\count1=\other + \advance\count1 by 1 + \ifnum \count1<256 \loop \fi + }% + }% + % + % @ is our escape character in .aux files, and we need braces. + \catcode`\{=1 + \catcode`\}=2 + \catcode`\@=0 +} + +\def\readdatafile#1{% +\begingroup + \setupdatafile + \input\jobname.#1 +\endgroup} + +\message{insertions,} +% including footnotes. + +\newcount \footnoteno + +% The trailing space in the following definition for supereject is +% vital for proper filling; pages come out unaligned when you do a +% pagealignmacro call if that space before the closing brace is +% removed. (Generally, numeric constants should always be followed by a +% space to prevent strange expansion errors.) +\def\supereject{\par\penalty -20000\footnoteno =0 } + +% @footnotestyle is meaningful for info output only. +\let\footnotestyle=\comment + +{\catcode `\@=11 +% +% Auto-number footnotes. Otherwise like plain. +\gdef\footnote{% + \let\indent=\ptexindent + \let\noindent=\ptexnoindent + \global\advance\footnoteno by \@ne + \edef\thisfootno{$^{\the\footnoteno}$}% + % + % In case the footnote comes at the end of a sentence, preserve the + % extra spacing after we do the footnote number. + \let\@sf\empty + \ifhmode\edef\@sf{\spacefactor\the\spacefactor}\ptexslash\fi + % + % Remove inadvertent blank space before typesetting the footnote number. + \unskip + \thisfootno\@sf + \dofootnote +}% + +% Don't bother with the trickery in plain.tex to not require the +% footnote text as a parameter. Our footnotes don't need to be so general. +% +% Oh yes, they do; otherwise, @ifset (and anything else that uses +% \parseargline) fails inside footnotes because the tokens are fixed when +% the footnote is read. --karl, 16nov96. +% +\gdef\dofootnote{% + \insert\footins\bgroup + % We want to typeset this text as a normal paragraph, even if the + % footnote reference occurs in (for example) a display environment. + % So reset some parameters. + \hsize=\pagewidth + \interlinepenalty\interfootnotelinepenalty + \splittopskip\ht\strutbox % top baseline for broken footnotes + \splitmaxdepth\dp\strutbox + \floatingpenalty\@MM + \leftskip\z@skip + \rightskip\z@skip + \spaceskip\z@skip + \xspaceskip\z@skip + \parindent\defaultparindent + % + \smallfonts \rm + % + % Because we use hanging indentation in footnotes, a @noindent appears + % to exdent this text, so make it be a no-op. makeinfo does not use + % hanging indentation so @noindent can still be needed within footnote + % text after an @example or the like (not that this is good style). + \let\noindent = \relax + % + % Hang the footnote text off the number. Use \everypar in case the + % footnote extends for more than one paragraph. + \everypar = {\hang}% + \textindent{\thisfootno}% + % + % Don't crash into the line above the footnote text. Since this + % expands into a box, it must come within the paragraph, lest it + % provide a place where TeX can split the footnote. + \footstrut + \futurelet\next\fo@t +} +}%end \catcode `\@=11 + +% In case a @footnote appears in a vbox, save the footnote text and create +% the real \insert just after the vbox finished. Otherwise, the insertion +% would be lost. +% Similarily, if a @footnote appears inside an alignment, save the footnote +% text to a box and make the \insert when a row of the table is finished. +% And the same can be done for other insert classes. --kasal, 16nov03. + +% Replace the \insert primitive by a cheating macro. +% Deeper inside, just make sure that the saved insertions are not spilled +% out prematurely. +% +\def\startsavinginserts{% + \ifx \insert\ptexinsert + \let\insert\saveinsert + \else + \let\checkinserts\relax + \fi +} + +% This \insert replacement works for both \insert\footins{foo} and +% \insert\footins\bgroup foo\egroup, but it doesn't work for \insert27{foo}. +% +\def\saveinsert#1{% + \edef\next{\noexpand\savetobox \makeSAVEname#1}% + \afterassignment\next + % swallow the left brace + \let\temp = +} +\def\makeSAVEname#1{\makecsname{SAVE\expandafter\gobble\string#1}} +\def\savetobox#1{\global\setbox#1 = \vbox\bgroup \unvbox#1} + +\def\checksaveins#1{\ifvoid#1\else \placesaveins#1\fi} + +\def\placesaveins#1{% + \ptexinsert \csname\expandafter\gobblesave\string#1\endcsname + {\box#1}% +} + +% eat @SAVE -- beware, all of them have catcode \other: +{ + \def\dospecials{\do S\do A\do V\do E} \uncatcodespecials % ;-) + \gdef\gobblesave @SAVE{} +} + +% initialization: +\def\newsaveins #1{% + \edef\next{\noexpand\newsaveinsX \makeSAVEname#1}% + \next +} +\def\newsaveinsX #1{% + \csname newbox\endcsname #1% + \expandafter\def\expandafter\checkinserts\expandafter{\checkinserts + \checksaveins #1}% +} + +% initialize: +\let\checkinserts\empty +\newsaveins\footins +\newsaveins\margin + + +% @image. We use the macros from epsf.tex to support this. +% If epsf.tex is not installed and @image is used, we complain. +% +% Check for and read epsf.tex up front. If we read it only at @image +% time, we might be inside a group, and then its definitions would get +% undone and the next image would fail. +\openin 1 = epsf.tex +\ifeof 1 \else + % Do not bother showing banner with epsf.tex v2.7k (available in + % doc/epsf.tex and on ctan). + \def\epsfannounce{\toks0 = }% + \input epsf.tex +\fi +\closein 1 +% +% We will only complain once about lack of epsf.tex. +\newif\ifwarnednoepsf +\newhelp\noepsfhelp{epsf.tex must be installed for images to + work. It is also included in the Texinfo distribution, or you can get + it from ftp://tug.org/tex/epsf.tex.} +% +\def\image#1{% + \ifx\epsfbox\undefined + \ifwarnednoepsf \else + \errhelp = \noepsfhelp + \errmessage{epsf.tex not found, images will be ignored}% + \global\warnednoepsftrue + \fi + \else + \imagexxx #1,,,,,\finish + \fi +} +% +% Arguments to @image: +% #1 is (mandatory) image filename; we tack on .eps extension. +% #2 is (optional) width, #3 is (optional) height. +% #4 is (ignored optional) html alt text. +% #5 is (ignored optional) extension. +% #6 is just the usual extra ignored arg for parsing this stuff. +\newif\ifimagevmode +\def\imagexxx#1,#2,#3,#4,#5,#6\finish{\begingroup + \catcode`\^^M = 5 % in case we're inside an example + \normalturnoffactive % allow _ et al. in names + % If the image is by itself, center it. + \ifvmode + \imagevmodetrue + \nobreak\bigskip + % Usually we'll have text after the image which will insert + % \parskip glue, so insert it here too to equalize the space + % above and below. + \nobreak\vskip\parskip + \nobreak + \line\bgroup + \fi + % + % Output the image. + \ifpdf + \dopdfimage{#1}{#2}{#3}% + \else + % \epsfbox itself resets \epsf?size at each figure. + \setbox0 = \hbox{\ignorespaces #2}\ifdim\wd0 > 0pt \epsfxsize=#2\relax \fi + \setbox0 = \hbox{\ignorespaces #3}\ifdim\wd0 > 0pt \epsfysize=#3\relax \fi + \epsfbox{#1.eps}% + \fi + % + \ifimagevmode \egroup \bigbreak \fi % space after the image +\endgroup} + + +% @float FLOATTYPE,LABEL,LOC ... @end float for displayed figures, tables, +% etc. We don't actually implement floating yet, we always include the +% float "here". But it seemed the best name for the future. +% +\envparseargdef\float{\eatcommaspace\eatcommaspace\dofloat#1, , ,\finish} + +% There may be a space before second and/or third parameter; delete it. +\def\eatcommaspace#1, {#1,} + +% #1 is the optional FLOATTYPE, the text label for this float, typically +% "Figure", "Table", "Example", etc. Can't contain commas. If omitted, +% this float will not be numbered and cannot be referred to. +% +% #2 is the optional xref label. Also must be present for the float to +% be referable. +% +% #3 is the optional positioning argument; for now, it is ignored. It +% will somehow specify the positions allowed to float to (here, top, bottom). +% +% We keep a separate counter for each FLOATTYPE, which we reset at each +% chapter-level command. +\let\resetallfloatnos=\empty +% +\def\dofloat#1,#2,#3,#4\finish{% + \let\thiscaption=\empty + \let\thisshortcaption=\empty + % + % don't lose footnotes inside @float. + % + % BEWARE: when the floats start float, we have to issue warning whenever an + % insert appears inside a float which could possibly float. --kasal, 26may04 + % + \startsavinginserts + % + % We can't be used inside a paragraph. + \par + % + \vtop\bgroup + \def\floattype{#1}% + \def\floatlabel{#2}% + \def\floatloc{#3}% we do nothing with this yet. + % + \ifx\floattype\empty + \let\safefloattype=\empty + \else + {% + % the floattype might have accents or other special characters, + % but we need to use it in a control sequence name. + \indexnofonts + \turnoffactive + \xdef\safefloattype{\floattype}% + }% + \fi + % + % If label is given but no type, we handle that as the empty type. + \ifx\floatlabel\empty \else + % We want each FLOATTYPE to be numbered separately (Figure 1, + % Table 1, Figure 2, ...). (And if no label, no number.) + % + \expandafter\getfloatno\csname\safefloattype floatno\endcsname + \global\advance\floatno by 1 + % + {% + % This magic value for \thissection is output by \setref as the + % XREFLABEL-title value. \xrefX uses it to distinguish float + % labels (which have a completely different output format) from + % node and anchor labels. And \xrdef uses it to construct the + % lists of floats. + % + \edef\thissection{\floatmagic=\safefloattype}% + \setref{\floatlabel}{Yfloat}% + }% + \fi + % + % start with \parskip glue, I guess. + \vskip\parskip + % + % Don't suppress indentation if a float happens to start a section. + \restorefirstparagraphindent +} + +% we have these possibilities: +% @float Foo,lbl & @caption{Cap}: Foo 1.1: Cap +% @float Foo,lbl & no caption: Foo 1.1 +% @float Foo & @caption{Cap}: Foo: Cap +% @float Foo & no caption: Foo +% @float ,lbl & Caption{Cap}: 1.1: Cap +% @float ,lbl & no caption: 1.1 +% @float & @caption{Cap}: Cap +% @float & no caption: +% +\def\Efloat{% + \let\floatident = \empty + % + % In all cases, if we have a float type, it comes first. + \ifx\floattype\empty \else \def\floatident{\floattype}\fi + % + % If we have an xref label, the number comes next. + \ifx\floatlabel\empty \else + \ifx\floattype\empty \else % if also had float type, need tie first. + \appendtomacro\floatident{\tie}% + \fi + % the number. + \appendtomacro\floatident{\chaplevelprefix\the\floatno}% + \fi + % + % Start the printed caption with what we've constructed in + % \floatident, but keep it separate; we need \floatident again. + \let\captionline = \floatident + % + \ifx\thiscaption\empty \else + \ifx\floatident\empty \else + \appendtomacro\captionline{: }% had ident, so need a colon between + \fi + % + % caption text. + \appendtomacro\captionline{\scanexp\thiscaption}% + \fi + % + % If we have anything to print, print it, with space before. + % Eventually this needs to become an \insert. + \ifx\captionline\empty \else + \vskip.5\parskip + \captionline + % + % Space below caption. + \vskip\parskip + \fi + % + % If have an xref label, write the list of floats info. Do this + % after the caption, to avoid chance of it being a breakpoint. + \ifx\floatlabel\empty \else + % Write the text that goes in the lof to the aux file as + % \floatlabel-lof. Besides \floatident, we include the short + % caption if specified, else the full caption if specified, else nothing. + {% + \atdummies + % + % since we read the caption text in the macro world, where ^^M + % is turned into a normal character, we have to scan it back, so + % we don't write the literal three characters "^^M" into the aux file. + \scanexp{% + \xdef\noexpand\gtemp{% + \ifx\thisshortcaption\empty + \thiscaption + \else + \thisshortcaption + \fi + }% + }% + \immediate\write\auxfile{@xrdef{\floatlabel-lof}{\floatident + \ifx\gtemp\empty \else : \gtemp \fi}}% + }% + \fi + \egroup % end of \vtop + % + % place the captured inserts + % + % BEWARE: when the floats start floating, we have to issue warning + % whenever an insert appears inside a float which could possibly + % float. --kasal, 26may04 + % + \checkinserts +} + +% Append the tokens #2 to the definition of macro #1, not expanding either. +% +\def\appendtomacro#1#2{% + \expandafter\def\expandafter#1\expandafter{#1#2}% +} + +% @caption, @shortcaption +% +\def\caption{\docaption\thiscaption} +\def\shortcaption{\docaption\thisshortcaption} +\def\docaption{\checkenv\float \bgroup\scanargctxt\defcaption} +\def\defcaption#1#2{\egroup \def#1{#2}} + +% The parameter is the control sequence identifying the counter we are +% going to use. Create it if it doesn't exist and assign it to \floatno. +\def\getfloatno#1{% + \ifx#1\relax + % Haven't seen this figure type before. + \csname newcount\endcsname #1% + % + % Remember to reset this floatno at the next chap. + \expandafter\gdef\expandafter\resetallfloatnos + \expandafter{\resetallfloatnos #1=0 }% + \fi + \let\floatno#1% +} + +% \setref calls this to get the XREFLABEL-snt value. We want an @xref +% to the FLOATLABEL to expand to "Figure 3.1". We call \setref when we +% first read the @float command. +% +\def\Yfloat{\floattype@tie \chaplevelprefix\the\floatno}% + +% Magic string used for the XREFLABEL-title value, so \xrefX can +% distinguish floats from other xref types. +\def\floatmagic{!!float!!} + +% #1 is the control sequence we are passed; we expand into a conditional +% which is true if #1 represents a float ref. That is, the magic +% \thissection value which we \setref above. +% +\def\iffloat#1{\expandafter\doiffloat#1==\finish} +% +% #1 is (maybe) the \floatmagic string. If so, #2 will be the +% (safe) float type for this float. We set \iffloattype to #2. +% +\def\doiffloat#1=#2=#3\finish{% + \def\temp{#1}% + \def\iffloattype{#2}% + \ifx\temp\floatmagic +} + +% @listoffloats FLOATTYPE - print a list of floats like a table of contents. +% +\parseargdef\listoffloats{% + \def\floattype{#1}% floattype + {% + % the floattype might have accents or other special characters, + % but we need to use it in a control sequence name. + \indexnofonts + \turnoffactive + \xdef\safefloattype{\floattype}% + }% + % + % \xrdef saves the floats as a \do-list in \floatlistSAFEFLOATTYPE. + \expandafter\ifx\csname floatlist\safefloattype\endcsname \relax + \ifhavexrefs + % if the user said @listoffloats foo but never @float foo. + \message{\linenumber No `\safefloattype' floats to list.}% + \fi + \else + \begingroup + \leftskip=\tocindent % indent these entries like a toc + \let\do=\listoffloatsdo + \csname floatlist\safefloattype\endcsname + \endgroup + \fi +} + +% This is called on each entry in a list of floats. We're passed the +% xref label, in the form LABEL-title, which is how we save it in the +% aux file. We strip off the -title and look up \XRLABEL-lof, which +% has the text we're supposed to typeset here. +% +% Figures without xref labels will not be included in the list (since +% they won't appear in the aux file). +% +\def\listoffloatsdo#1{\listoffloatsdoentry#1\finish} +\def\listoffloatsdoentry#1-title\finish{{% + % Can't fully expand XR#1-lof because it can contain anything. Just + % pass the control sequence. On the other hand, XR#1-pg is just the + % page number, and we want to fully expand that so we can get a link + % in pdf output. + \toksA = \expandafter{\csname XR#1-lof\endcsname}% + % + % use the same \entry macro we use to generate the TOC and index. + \edef\writeentry{\noexpand\entry{\the\toksA}{\csname XR#1-pg\endcsname}}% + \writeentry +}} + +\message{localization,} +% and i18n. + +% @documentlanguage is usually given very early, just after +% @setfilename. If done too late, it may not override everything +% properly. Single argument is the language abbreviation. +% It would be nice if we could set up a hyphenation file here. +% +\parseargdef\documentlanguage{% + \tex % read txi-??.tex file in plain TeX. + % Read the file if it exists. + \openin 1 txi-#1.tex + \ifeof 1 + \errhelp = \nolanghelp + \errmessage{Cannot read language file txi-#1.tex}% + \else + \input txi-#1.tex + \fi + \closein 1 + \endgroup +} +\newhelp\nolanghelp{The given language definition file cannot be found or +is empty. Maybe you need to install it? In the current directory +should work if nowhere else does.} + + +% @documentencoding should change something in TeX eventually, most +% likely, but for now just recognize it. +\let\documentencoding = \comment + + +% Page size parameters. +% +\newdimen\defaultparindent \defaultparindent = 15pt + +\chapheadingskip = 15pt plus 4pt minus 2pt +\secheadingskip = 12pt plus 3pt minus 2pt +\subsecheadingskip = 9pt plus 2pt minus 2pt + +% Prevent underfull vbox error messages. +\vbadness = 10000 + +% Don't be so finicky about underfull hboxes, either. +\hbadness = 2000 + +% Following George Bush, just get rid of widows and orphans. +\widowpenalty=10000 +\clubpenalty=10000 + +% Use TeX 3.0's \emergencystretch to help line breaking, but if we're +% using an old version of TeX, don't do anything. We want the amount of +% stretch added to depend on the line length, hence the dependence on +% \hsize. We call this whenever the paper size is set. +% +\def\setemergencystretch{% + \ifx\emergencystretch\thisisundefined + % Allow us to assign to \emergencystretch anyway. + \def\emergencystretch{\dimen0}% + \else + \emergencystretch = .15\hsize + \fi +} + +% Parameters in order: 1) textheight; 2) textwidth; +% 3) voffset; 4) hoffset; 5) binding offset; 6) topskip; +% 7) physical page height; 8) physical page width. +% +% We also call \setleading{\textleading}, so the caller should define +% \textleading. The caller should also set \parskip. +% +\def\internalpagesizes#1#2#3#4#5#6#7#8{% + \voffset = #3\relax + \topskip = #6\relax + \splittopskip = \topskip + % + \vsize = #1\relax + \advance\vsize by \topskip + \outervsize = \vsize + \advance\outervsize by 2\topandbottommargin + \pageheight = \vsize + % + \hsize = #2\relax + \outerhsize = \hsize + \advance\outerhsize by 0.5in + \pagewidth = \hsize + % + \normaloffset = #4\relax + \bindingoffset = #5\relax + % + \ifpdf + \pdfpageheight #7\relax + \pdfpagewidth #8\relax + \fi + % + \setleading{\textleading} + % + \parindent = \defaultparindent + \setemergencystretch +} + +% @letterpaper (the default). +\def\letterpaper{{\globaldefs = 1 + \parskip = 3pt plus 2pt minus 1pt + \textleading = 13.2pt + % + % If page is nothing but text, make it come out even. + \internalpagesizes{46\baselineskip}{6in}% + {\voffset}{.25in}% + {\bindingoffset}{36pt}% + {11in}{8.5in}% +}} + +% Use @smallbook to reset parameters for 7x9.25 trim size. +\def\smallbook{{\globaldefs = 1 + \parskip = 2pt plus 1pt + \textleading = 12pt + % + \internalpagesizes{7.5in}{5in}% + {\voffset}{.25in}% + {\bindingoffset}{16pt}% + {9.25in}{7in}% + % + \lispnarrowing = 0.3in + \tolerance = 700 + \hfuzz = 1pt + \contentsrightmargin = 0pt + \defbodyindent = .5cm +}} + +% Use @smallerbook to reset parameters for 6x9 trim size. +% (Just testing, parameters still in flux.) +\def\smallerbook{{\globaldefs = 1 + \parskip = 1.5pt plus 1pt + \textleading = 12pt + % + \internalpagesizes{7.4in}{4.8in}% + {-.2in}{-.4in}% + {0pt}{14pt}% + {9in}{6in}% + % + \lispnarrowing = 0.25in + \tolerance = 700 + \hfuzz = 1pt + \contentsrightmargin = 0pt + \defbodyindent = .4cm +}} + +% Use @afourpaper to print on European A4 paper. +\def\afourpaper{{\globaldefs = 1 + \parskip = 3pt plus 2pt minus 1pt + \textleading = 13.2pt + % + % Double-side printing via postscript on Laserjet 4050 + % prints double-sided nicely when \bindingoffset=10mm and \hoffset=-6mm. + % To change the settings for a different printer or situation, adjust + % \normaloffset until the front-side and back-side texts align. Then + % do the same for \bindingoffset. You can set these for testing in + % your texinfo source file like this: + % @tex + % \global\normaloffset = -6mm + % \global\bindingoffset = 10mm + % @end tex + \internalpagesizes{51\baselineskip}{160mm} + {\voffset}{\hoffset}% + {\bindingoffset}{44pt}% + {297mm}{210mm}% + % + \tolerance = 700 + \hfuzz = 1pt + \contentsrightmargin = 0pt + \defbodyindent = 5mm +}} + +% Use @afivepaper to print on European A5 paper. +% From romildo@urano.iceb.ufop.br, 2 July 2000. +% He also recommends making @example and @lisp be small. +\def\afivepaper{{\globaldefs = 1 + \parskip = 2pt plus 1pt minus 0.1pt + \textleading = 12.5pt + % + \internalpagesizes{160mm}{120mm}% + {\voffset}{\hoffset}% + {\bindingoffset}{8pt}% + {210mm}{148mm}% + % + \lispnarrowing = 0.2in + \tolerance = 800 + \hfuzz = 1.2pt + \contentsrightmargin = 0pt + \defbodyindent = 2mm + \tableindent = 12mm +}} + +% A specific text layout, 24x15cm overall, intended for A4 paper. +\def\afourlatex{{\globaldefs = 1 + \afourpaper + \internalpagesizes{237mm}{150mm}% + {\voffset}{4.6mm}% + {\bindingoffset}{7mm}% + {297mm}{210mm}% + % + % Must explicitly reset to 0 because we call \afourpaper. + \globaldefs = 0 +}} + +% Use @afourwide to print on A4 paper in landscape format. +\def\afourwide{{\globaldefs = 1 + \afourpaper + \internalpagesizes{241mm}{165mm}% + {\voffset}{-2.95mm}% + {\bindingoffset}{7mm}% + {297mm}{210mm}% + \globaldefs = 0 +}} + +% @pagesizes TEXTHEIGHT[,TEXTWIDTH] +% Perhaps we should allow setting the margins, \topskip, \parskip, +% and/or leading, also. Or perhaps we should compute them somehow. +% +\parseargdef\pagesizes{\pagesizesyyy #1,,\finish} +\def\pagesizesyyy#1,#2,#3\finish{{% + \setbox0 = \hbox{\ignorespaces #2}\ifdim\wd0 > 0pt \hsize=#2\relax \fi + \globaldefs = 1 + % + \parskip = 3pt plus 2pt minus 1pt + \setleading{\textleading}% + % + \dimen0 = #1 + \advance\dimen0 by \voffset + % + \dimen2 = \hsize + \advance\dimen2 by \normaloffset + % + \internalpagesizes{#1}{\hsize}% + {\voffset}{\normaloffset}% + {\bindingoffset}{44pt}% + {\dimen0}{\dimen2}% +}} + +% Set default to letter. +% +\letterpaper + + +\message{and turning on texinfo input format.} + +% Define macros to output various characters with catcode for normal text. +\catcode`\"=\other +\catcode`\~=\other +\catcode`\^=\other +\catcode`\_=\other +\catcode`\|=\other +\catcode`\<=\other +\catcode`\>=\other +\catcode`\+=\other +\catcode`\$=\other +\def\normaldoublequote{"} +\def\normaltilde{~} +\def\normalcaret{^} +\def\normalunderscore{_} +\def\normalverticalbar{|} +\def\normalless{<} +\def\normalgreater{>} +\def\normalplus{+} +\def\normaldollar{$}%$ font-lock fix + +% This macro is used to make a character print one way in \tt +% (where it can probably be output as-is), and another way in other fonts, +% where something hairier probably needs to be done. +% +% #1 is what to print if we are indeed using \tt; #2 is what to print +% otherwise. Since all the Computer Modern typewriter fonts have zero +% interword stretch (and shrink), and it is reasonable to expect all +% typewriter fonts to have this, we can check that font parameter. +% +\def\ifusingtt#1#2{\ifdim \fontdimen3\font=0pt #1\else #2\fi} + +% Same as above, but check for italic font. Actually this also catches +% non-italic slanted fonts since it is impossible to distinguish them from +% italic fonts. But since this is only used by $ and it uses \sl anyway +% this is not a problem. +\def\ifusingit#1#2{\ifdim \fontdimen1\font>0pt #1\else #2\fi} + +% Turn off all special characters except @ +% (and those which the user can use as if they were ordinary). +% Most of these we simply print from the \tt font, but for some, we can +% use math or other variants that look better in normal text. + +\catcode`\"=\active +\def\activedoublequote{{\tt\char34}} +\let"=\activedoublequote +\catcode`\~=\active +\def~{{\tt\char126}} +\chardef\hat=`\^ +\catcode`\^=\active +\def^{{\tt \hat}} + +\catcode`\_=\active +\def_{\ifusingtt\normalunderscore\_} +\let\realunder=_ +% Subroutine for the previous macro. +\def\_{\leavevmode \kern.07em \vbox{\hrule width.3em height.1ex}\kern .07em } + +\catcode`\|=\active +\def|{{\tt\char124}} +\chardef \less=`\< +\catcode`\<=\active +\def<{{\tt \less}} +\chardef \gtr=`\> +\catcode`\>=\active +\def>{{\tt \gtr}} +\catcode`\+=\active +\def+{{\tt \char 43}} +\catcode`\$=\active +\def${\ifusingit{{\sl\$}}\normaldollar}%$ font-lock fix + +% If a .fmt file is being used, characters that might appear in a file +% name cannot be active until we have parsed the command line. +% So turn them off again, and have \everyjob (or @setfilename) turn them on. +% \otherifyactive is called near the end of this file. +\def\otherifyactive{\catcode`+=\other \catcode`\_=\other} + +% Used sometimes to turn off (effectively) the active characters even after +% parsing them. +\def\turnoffactive{% + \normalturnoffactive + \otherbackslash +} + +\catcode`\@=0 + +% \backslashcurfont outputs one backslash character in current font, +% as in \char`\\. +\global\chardef\backslashcurfont=`\\ +\global\let\rawbackslashxx=\backslashcurfont % let existing .??s files work + +% \realbackslash is an actual character `\' with catcode other, and +% \doublebackslash is two of them (for the pdf outlines). +{\catcode`\\=\other @gdef@realbackslash{\} @gdef@doublebackslash{\\}} + +% In texinfo, backslash is an active character; it prints the backslash +% in fixed width font. +\catcode`\\=\active +@def@normalbackslash{{@tt@backslashcurfont}} +% On startup, @fixbackslash assigns: +% @let \ = @normalbackslash + +% \rawbackslash defines an active \ to do \backslashcurfont. +% \otherbackslash defines an active \ to be a literal `\' character with +% catcode other. +@gdef@rawbackslash{@let\=@backslashcurfont} +@gdef@otherbackslash{@let\=@realbackslash} + +% Same as @turnoffactive except outputs \ as {\tt\char`\\} instead of +% the literal character `\'. +% +@def@normalturnoffactive{% + @let\=@normalbackslash + @let"=@normaldoublequote + @let~=@normaltilde + @let^=@normalcaret + @let_=@normalunderscore + @let|=@normalverticalbar + @let<=@normalless + @let>=@normalgreater + @let+=@normalplus + @let$=@normaldollar %$ font-lock fix + @unsepspaces +} + +% Make _ and + \other characters, temporarily. +% This is canceled by @fixbackslash. +@otherifyactive + +% If a .fmt file is being used, we don't want the `\input texinfo' to show up. +% That is what \eatinput is for; after that, the `\' should revert to printing +% a backslash. +% +@gdef@eatinput input texinfo{@fixbackslash} +@global@let\ = @eatinput + +% On the other hand, perhaps the file did not have a `\input texinfo'. Then +% the first `\' in the file would cause an error. This macro tries to fix +% that, assuming it is called before the first `\' could plausibly occur. +% Also turn back on active characters that might appear in the input +% file name, in case not using a pre-dumped format. +% +@gdef@fixbackslash{% + @ifx\@eatinput @let\ = @normalbackslash @fi + @catcode`+=@active + @catcode`@_=@active +} + +% Say @foo, not \foo, in error messages. +@escapechar = `@@ + +% These look ok in all fonts, so just make them not special. +@catcode`@& = @other +@catcode`@# = @other +@catcode`@% = @other + + +@c Local variables: +@c eval: (add-hook 'write-file-hooks 'time-stamp) +@c page-delimiter: "^\\\\message" +@c time-stamp-start: "def\\\\texinfoversion{" +@c time-stamp-format: "%:y-%02m-%02d.%02H" +@c time-stamp-end: "}" +@c End: + +@c vim:sw=2: + +@ignore + arch-tag: e1b36e32-c96e-4135-a41a-0b2efa2ea115 +@end ignore diff --git a/lbtt/src/BitArray.cc b/lbtt/src/BitArray.cc index f2086ab47..3c840c6a1 100644 --- a/lbtt/src/BitArray.cc +++ b/lbtt/src/BitArray.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,10 +17,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #ifdef HAVE_SSTREAM #include @@ -378,7 +374,7 @@ unsigned long int BitArray::find(const unsigned long int max_count) const for (i = 0; i < bsize && bits[i] == 0; ++i) ; - if (i == max_count) + if (i == bsize) return max_count; unsigned char c = bits[i]; diff --git a/lbtt/src/BitArray.h b/lbtt/src/BitArray.h index d8ef12b96..68ee05285 100644 --- a/lbtt/src/BitArray.h +++ b/lbtt/src/BitArray.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,10 +20,6 @@ #ifndef BITARRAY_H #define BITARRAY_H -#ifdef __GNUC__ -#pragma interface -#endif /* __GNUC__ */ - #include #include #include @@ -363,7 +359,10 @@ inline void BitArray::set(const unsigned long int bit_count) * * ------------------------------------------------------------------------- */ { - memset(static_cast(bits), 0xFF, (bit_count + 7) >> 3); + unsigned long int bsize = bit_count >> 3; + memset(static_cast(bits), 0xFF, bsize); + if ((bit_count & 0x07) != 0) + bits[bsize] |= (1 << (bit_count & 7)) - 1; } /* ========================================================================= */ @@ -394,7 +393,10 @@ inline void BitArray::clear(const unsigned long int bit_count) * * ------------------------------------------------------------------------- */ { - memset(static_cast(bits), 0, (bit_count + 7) >> 3); + unsigned long int bsize = bit_count >> 3; + memset(static_cast(bits), 0, bsize); + if ((bit_count & 0x07) != 0) + bits[bsize] &= ~((1 << (bit_count & 7)) - 1); } /* ========================================================================= */ diff --git a/lbtt/src/Bitset.h b/lbtt/src/Bitset.h index 82f87667c..a7536d5ad 100644 --- a/lbtt/src/Bitset.h +++ b/lbtt/src/Bitset.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lbtt/src/BuchiAutomaton.cc b/lbtt/src/BuchiAutomaton.cc index 7be826c8e..318c75b67 100644 --- a/lbtt/src/BuchiAutomaton.cc +++ b/lbtt/src/BuchiAutomaton.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,10 +17,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #include #include @@ -96,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); @@ -133,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); @@ -194,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. * @@ -316,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 @@ -345,27 +288,20 @@ void BuchiAutomaton::read(istream& input_stream) * to the interval [0...(number of states - 1)]. */ - map, ALLOC(size_type) > - state_number_map; + map state_number_map; pair state_mapping(0, 0); - pair, ALLOC(size_type) > - ::const_iterator, - bool> - state_finder; + pair::const_iterator, bool> state_finder; /* * Also the acceptance set numbers will be mapped to the interval * [0...(number of acceptance sets - 1)]. */ - map, ALLOC(unsigned long int) > - acceptance_set_map; + map acceptance_set_map; pair acceptance_set_mapping(0, 0); - pair, - ALLOC(unsigned long int) >::const_iterator, - bool> + pair::const_iterator, bool> acceptance_set_finder; /* @@ -450,36 +386,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. */ @@ -505,6 +446,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); @@ -520,7 +497,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); @@ -548,297 +525,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 @@ -880,7 +566,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 @@ -941,7 +627,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"; } } @@ -964,16 +651,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. * * ------------------------------------------------------------------------- */ @@ -981,11 +675,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)); @@ -1006,9 +698,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(); } @@ -1073,7 +789,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..cfd212323 100644 --- a/lbtt/src/BuchiAutomaton.h +++ b/lbtt/src/BuchiAutomaton.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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 index c5f5c51d5..d3a6157b6 100644 --- a/lbtt/src/BuchiProduct.cc +++ b/lbtt/src/BuchiProduct.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,8 +28,7 @@ namespace Graph * *****************************************************************************/ -map< ::Ltl::LtlFormula*, BuchiProduct::SatisfiabilityMapping, - less< ::Ltl::LtlFormula*>, ALLOC(BuchiProduct::SatisfiabilityMapping) > +map< ::Ltl::LtlFormula*, BuchiProduct::SatisfiabilityMapping> BuchiProduct::sat_cache; @@ -73,8 +72,7 @@ bool BuchiProduct::synchronizable guard_1 = swap_guard; } - map, - ALLOC(SatisfiabilityMapping) >::iterator + map::iterator sat_cache_element = sat_cache.find(guard_1); if (sat_cache_element == sat_cache.end()) diff --git a/lbtt/src/BuchiProduct.h b/lbtt/src/BuchiProduct.h index 43200f521..c431d8d36 100644 --- a/lbtt/src/BuchiProduct.h +++ b/lbtt/src/BuchiProduct.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -142,18 +142,16 @@ private: * automata. */ - typedef map< ::Ltl::LtlFormula*, bool, /* Type definition for */ - less< ::Ltl::LtlFormula*>, /* storing information */ - ALLOC(bool) > /* about the */ - SatisfiabilityMapping; /* satisfiability of the + typedef map< ::Ltl::LtlFormula*, bool> /* Type definition for */ + SatisfiabilityMapping; /* storing information + * about the + * satisfiability of the * guards of product * transitions. */ static map< ::Ltl::LtlFormula*, /* Result cache for */ - SatisfiabilityMapping, /* satisfiability tests. */ - less< ::Ltl::LtlFormula*>, - ALLOC(SatisfiabilityMapping) > + SatisfiabilityMapping> /* satisfiability tests. */ sat_cache; }; diff --git a/lbtt/src/Config-lex.ll b/lbtt/src/Config-lex.ll index e09e51308..ea0af40f9 100644 --- a/lbtt/src/Config-lex.ll +++ b/lbtt/src/Config-lex.ll @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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..b5539afbe 100644 --- a/lbtt/src/Config-parse.yy +++ b/lbtt/src/Config-parse.yy @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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..0dd3e99be 100644 --- a/lbtt/src/Configuration.cc +++ b/lbtt/src/Configuration.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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"}; + /****************************************************************************** @@ -132,13 +96,12 @@ Configuration::~Configuration() * * ------------------------------------------------------------------------- */ { - for (vector - ::const_iterator it = algorithms.begin(); + for (vector::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 +123,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 +139,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 +162,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 +182,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 +211,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 +266,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 +342,369 @@ 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::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 +720,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 +728,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 +747,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 @@ -985,8 +775,7 @@ void Configuration::read(int argc, char* argv[]) bool unary_operator_allowed = false; - for (map, ALLOC(int) >::iterator - it = formula_options.symbol_priority.begin(); + for (map::iterator it = formula_options.symbol_priority.begin(); it != formula_options.symbol_priority.end(); ++it) { if (it->second == -1) @@ -1000,7 +789,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"); /* @@ -1012,7 +801,7 @@ void Configuration::read(int argc, char* argv[]) int total_long_unary_priority = 0; int total_binary_priority = 0; - for (map, ALLOC(int) >::const_iterator + for (map::const_iterator it = formula_options.symbol_priority.begin(); it != formula_options.symbol_priority.end(); ++it) { @@ -1081,7 +870,7 @@ void Configuration::read(int argc, char* argv[]) k <= formula_options.formula_generator.max_size; k++) { - for (map, ALLOC(int) >::const_iterator + for (map::const_iterator op = formula_options.symbol_priority.begin(); op != formula_options.symbol_priority.end(); ++op) @@ -1175,6 +964,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 " @@ -1188,11 +981,9 @@ void Configuration::print(ostream& stream, int indent) const estream << '\n' + string(indent + 2, ' ') + "Implementations:\n"; - vector::size_type - algorithm_number = 0; + vector::size_type algorithm_number = 0; - for (vector - ::const_iterator a = algorithms.begin(); + for (vector::const_iterator a = algorithms.begin(); a != algorithms.end(); ++a) { @@ -1206,6 +997,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 @@ -1328,9 +1156,13 @@ void Configuration::print(ostream& stream, int indent) const number_of_available_variables == 1 ? "" : "s"); } else - estream << "Reading LTL formulas from `" - + global_options.formula_input_filename - + "'."; + { + estream << "Reading LTL formulas from "; + if (global_options.formula_input_filename == "-") + estream << "standard input."; + else + estream << "`" + global_options.formula_input_filename + "'."; + } estream << '\n' + string(indent + 4, ' '); @@ -1389,7 +1221,7 @@ void Configuration::print(ostream& stream, int indent) const bool first_printed = false; - for (map, ALLOC(int) >::const_iterator + for (map::const_iterator op = formula_options.symbol_priority.begin(); op != formula_options.symbol_priority.end(); ++op) @@ -1430,7 +1262,7 @@ void Configuration::print(ostream& stream, int indent) const int max_operators_per_line = (formula_options.symbol_distribution.empty() ? 7 : 6); - for (map, ALLOC(int) >::const_iterator op + for (map::const_iterator op = formula_options.symbol_priority.begin(); op != formula_options.symbol_priority.end(); ++op) @@ -1507,9 +1339,7 @@ void Configuration::print(ostream& stream, int indent) const /* ========================================================================= */ string Configuration::algorithmString - (vector::size_type - algorithm_id) const + (vector::size_type algorithm_id) const /* ---------------------------------------------------------------------------- * * Description: Constructs a string with an algorithm identifer and the name @@ -1521,10 +1351,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 +1369,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" @@ -1554,16 +1384,19 @@ void Configuration::showCommandLineHelp(const char* program_name) " --enable=IMPLEMENTATION-ID[,IMPLEMENTATION-ID,...]\n" " Include implementation(s) into " "tests\n" - " --formulafile=FILE Read LTL formulas from FILE\n" + " --formulafile=FILE Read LTL formulas from FILE " + "(- = standard input)\n" " --globalmodelcheck Use global model checking in " "tests\n" " (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 +1406,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 +1419,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 " @@ -1717,7 +1541,7 @@ void Configuration::showCommandLineHelp(const char* program_name) " --truthprobability=PROBABILITY\n" " Set truth probability of " "propositions (0.0--1.0)\n\n" - "Report bugs to .\n"; + "Report bugs to <" PACKAGE_BUGREPORT ">.\n"; } /* ========================================================================= */ @@ -1735,6 +1559,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 +1574,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 +1605,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..e7d9a1b56 100644 --- a/lbtt/src/Configuration.h +++ b/lbtt/src/Configuration.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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. @@ -63,21 +62,29 @@ public: struct AlgorithmInformation; /* See below. */ string algorithmString /* Formats the the id */ - (vector::size_type/* the name of the */ - algorithm_id) const; /* algorithm into a + (vector::size_type /* of an algorithm and */ + algorithm_id) const; /* the name of the + * algorithm into a * 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 @@ -290,11 +311,12 @@ public: * LTL formula symbols. */ - map, ALLOC(int) > /* Priorities for LTL */ - symbol_priority; /* formula symbols. */ + map symbol_priority; /* Priorities for LTL + * formula symbols. + */ - map, ALLOC(double) > /* Expected numbers of */ - symbol_distribution; /* occurrence for the + map symbol_distribution; /* Expected numbers of + * occurrence for the * different formula * operators. */ @@ -354,12 +376,18 @@ public: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - vector algorithms; /* information about the + vector algorithms; /* A vector containing + * information about the * algorithms used in * the tests. */ + map algorithm_names; /* Mapping between + * algorithm names and + * their numeric + * identifiers. + */ + GlobalConfiguration global_options; /* General configuration * information. */ @@ -376,15 +404,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 +438,9 @@ public: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +private: + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + struct IntegerRange /* Data structure for * representing integer- * valued ranges of certain @@ -426,11 +448,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 +461,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 +497,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, @@ -492,17 +512,18 @@ private: OPT_STATESPACEPROPOSITIONS, OPT_STATESPACESIZE, OPT_TRUTHPROBABILITY}; - typedef map, double, /* Type definitions for */ - less >, /* the result cache used */ - ALLOC(double) > /* for computing the */ - ProbabilityMapElement; /* probability */ - typedef map, /* formula operators. */ - ALLOC(ProbabilityMapElement) > - ProbabilityMap; + typedef map, double> /* Type definitions for */ + ProbabilityMapElement; /* the result cache used */ + /* for computing the */ + typedef map /* probability */ + ProbabilityMap; /* distribution of LTL + * formula operators. + */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + 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.cc b/lbtt/src/DispUtil.cc index 4bfa825e8..39f0b767f 100644 --- a/lbtt/src/DispUtil.cc +++ b/lbtt/src/DispUtil.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -32,10 +32,10 @@ namespace DispUtil { -stack > /* output stream. */ - stream_formatting_stack; +stack > /* Stack for storing the */ + stream_formatting_stack; /* previous states of an + * output stream. + */ /* ========================================================================= */ void changeStreamFormatting diff --git a/lbtt/src/DispUtil.h b/lbtt/src/DispUtil.h index ff0040c63..6f42e97d1 100644 --- a/lbtt/src/DispUtil.h +++ b/lbtt/src/DispUtil.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,10 +20,6 @@ #ifndef DISPUTIL_H #define DISPUTIL_H -#ifdef __GNUC__ -#pragma interface -#endif /* __GNUC__ */ - #include #include #include @@ -62,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); + /****************************************************************************** @@ -82,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/EdgeContainer.h b/lbtt/src/EdgeContainer.h index b0180304d..954eb8a36 100644 --- a/lbtt/src/EdgeContainer.h +++ b/lbtt/src/EdgeContainer.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lbtt/src/Exception.h b/lbtt/src/Exception.h index a429704b7..3ab2f3987 100644 --- a/lbtt/src/Exception.h +++ b/lbtt/src/Exception.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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 9a1043aa4..60cd80b9b 100644 --- a/lbtt/src/ExternalTranslator.cc +++ b/lbtt/src/ExternalTranslator.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,12 +18,11 @@ */ #include -#include #include #include +#include #include #include -#include #ifdef HAVE_FCNTL_H #include #endif /* HAVE_FCNTL_H */ @@ -64,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(); } /* ========================================================================= */ @@ -101,21 +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); - std::cout << translated_formula << std::endl; - 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); @@ -125,106 +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); - int exitcode = system(command_line.c_str()); - - /* - * system() blocks SIGINT and SIGQUIT. If the child was killed - * by such a signal, forward the signal to the current process. - */ - if (WIFSIGNALED(exitcode) && - (WTERMSIG(exitcode) == SIGINT || WTERMSIG(exitcode) == SIGQUIT)) - raise(WTERMSIG(exitcode)); - - if (!execSuccess(exitcode)) + 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/FormulaRandomizer.cc b/lbtt/src/FormulaRandomizer.cc index bdd5f87df..964360c1b 100644 --- a/lbtt/src/FormulaRandomizer.cc +++ b/lbtt/src/FormulaRandomizer.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,10 +17,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #include "FormulaRandomizer.h" #include "Random.h" @@ -96,7 +92,7 @@ LtlFormula* FormulaRandomizer::recGenerate(unsigned long int target_size) * * ------------------------------------------------------------------------- */ { - vector::const_iterator symbol_priority; + vector::const_iterator symbol_priority; LtlFormula* formula; long int x; diff --git a/lbtt/src/FormulaRandomizer.h b/lbtt/src/FormulaRandomizer.h index a0391d499..915625e58 100644 --- a/lbtt/src/FormulaRandomizer.h +++ b/lbtt/src/FormulaRandomizer.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -21,6 +21,7 @@ #define FORMULARANDOMIZER_H #include +#include #include #include #include "LbttAlloc.h" @@ -86,16 +87,16 @@ public: * `reset'. */ - const map, /* different atomic */ - ALLOC(unsigned long int) >& /* propositions */ - propositionStatistics() const; /* generated since the + const map& /* Get the numbers of */ + propositionStatistics() const; /* different atomic + * propositions + * generated since the * last call to `reset'. */ - const map, /* Get the numbers of */ - ALLOC(unsigned long int) >& /* different symbols */ - symbolStatistics() const; /* generated since the + const map& /* Get the numbers of */ + symbolStatistics() const; /* different symbols + * generated since the * last call to `reset'. */ @@ -123,21 +124,20 @@ private: typedef pair IntegerPair; - vector /* Operand symbols and */ + vector /* Operand symbols and */ propositional_symbol_priorities; /* their priorities in * random formulae. */ - vector /* Operators and their */ - short_formula_operators; /* priorities in random - * formulae of size - * two. + vector short_formula_operators; /* Operators and their + * priorities in random + * formulae of size two. */ - vector /* Operators and their */ - long_formula_operators; /* priorities in random - * formulae of size - * greater than two. + vector long_formula_operators; /* Operators and their + * priorities in random + * formulae of size greater + * than two. */ unsigned long int number_of_generated_formulas; /* Number of generated @@ -145,14 +145,15 @@ private: * last call to `reset'. */ - map, /* atomic propositions */ - ALLOC(unsigned long int) > /* generated since the */ - proposition_statistics; /* last call to `reset' */ + map /* Number of different */ + proposition_statistics; /* atomic propositions + * generated since the + * last call to `reset' + */ - map, /* Number of different */ - ALLOC(unsigned long int) > /* formula symbols */ - symbol_statistics; /* generated since the + map symbol_statistics; /* Number of different + * formula symbols + * generated since the * last call to `reset'. */ }; @@ -288,8 +289,7 @@ inline unsigned long int FormulaRandomizer::numberOfFormulas() const } /* ========================================================================= */ -inline const map, - ALLOC(unsigned long int) >& +inline const map& FormulaRandomizer::propositionStatistics() const /* ---------------------------------------------------------------------------- * @@ -307,7 +307,7 @@ FormulaRandomizer::propositionStatistics() const } /* ========================================================================= */ -inline const map, ALLOC(unsigned long int) >& +inline const map& FormulaRandomizer::symbolStatistics() const /* ---------------------------------------------------------------------------- * diff --git a/lbtt/src/FormulaWriter.h b/lbtt/src/FormulaWriter.h index 0df6ecfbf..feaf2121a 100644 --- a/lbtt/src/FormulaWriter.h +++ b/lbtt/src/FormulaWriter.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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..ec484f01f 100644 --- a/lbtt/src/Graph.h.in +++ b/lbtt/src/Graph.h.in @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -186,25 +186,38 @@ public: * graph nodes. */ + class PathElement; /* A class for representing + * (node, edge) pairs + */ + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ protected: - vector nodes; /* Nodes of the graph. + vector nodes; /* Nodes of the graph. * Derived classes can * access this vector * directly. */ public: - typedef typename /* Type definition for */ - vector::size_type /* the size of the */ - size_type; /* graph. The size can + typedef typename vector::size_type /* Type definition for */ + size_type; /* the size of the + * graph. The size can * be no greater than * the maximum size of * the vector containing * the graph nodes. */ + typedef EdgeContainer EdgeContainerType; /* Type definition for + * containers of graph + * edges. + */ + + typedef deque Path; /* Type definition for + * paths in a graph. + */ + typedef pair StateIdPair; /* Type definition for a * pair of state * identifiers in a graph. @@ -323,8 +336,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 +366,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 +417,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. @@ -434,8 +503,7 @@ public: * *****************************************************************************/ -class EdgeList : public list::Edge*, - ALLOC(Graph::Edge*) > +class EdgeList : public list::Edge*> { public: EdgeList(); /* Constructor. */ @@ -450,14 +518,12 @@ public: * the end of the list. */ - list::Edge*, /* Functions for finding */ - ALLOC(Graph::Edge*) > /* an element in the */ - ::const_iterator /* list. */ - find(const Graph::Edge* edge) const; - - list::Edge*, - ALLOC(Graph::Edge*) > - ::iterator + list::Edge*>::const_iterator /* Functions for finding */ + find(const Graph::Edge* edge) const; /* an element in the + * list. + */ + + list::Edge*>::iterator find(const Graph::Edge* edge); }; @@ -472,8 +538,7 @@ public: #ifdef HAVE_SLIST -class EdgeSlist : public slist::Edge*, - ALLOC(Graph::Edge*) > +class EdgeSlist : public slist::Edge*> { public: EdgeSlist(); /* Constructor. */ @@ -489,14 +554,12 @@ public: * list. */ - slist::Edge*, /* Functions for finding */ - ALLOC(Graph::Edge*) > /* an element in the */ - ::const_iterator /* list. */ - find(const Graph::Edge* edge) const; + slist::Edge*>::const_iterator /* Functions for finding */ + find(const Graph::Edge* edge) const; /* an element in the + * list. + */ - slist::Edge*, - ALLOC(Graph::Edge*) > - ::iterator + slist::Edge*>::iterator find(const Graph::Edge* edge); }; @@ -510,8 +573,7 @@ public: * *****************************************************************************/ -class EdgeVector : public vector::Edge*, - ALLOC(Graph::Edge*) > +class EdgeVector : public vector::Edge*> { public: EdgeVector(); /* Constructor. */ @@ -527,15 +589,11 @@ public: * to edges. */ - vector::Edge*, /* Functions for finding */ - ALLOC(Graph::Edge*) > /* an element in the */ - ::const_iterator /* container. */ - find(const Graph::Edge* edge) - const; + vector::Edge*>::const_iterator /* Functions for finding */ + find(const Graph::Edge* edge) /* an element in the */ + const; /* container. */ - vector::Edge*, - ALLOC(Graph::Edge*) > - ::iterator + vector::Edge*>::iterator find(const Graph::Edge* edge); }; @@ -548,8 +606,7 @@ public: *****************************************************************************/ class EdgeSet : public set::Edge*, - Graph::Edge::ptr_less, - ALLOC(Graph::Edge*) > + Graph::Edge::ptr_less> { }; @@ -562,8 +619,7 @@ class EdgeSet : public set::Edge*, *****************************************************************************/ class EdgeMultiSet : public multiset::Edge*, - Graph::Edge::ptr_less, - ALLOC(Graph::Edge*) > + Graph::Edge::ptr_less> { }; @@ -689,7 +745,7 @@ Graph::Graph(const size_type initial_number_of_nodes) : { nodes.reserve(initial_number_of_nodes); - for (typename vector::iterator node = nodes.begin(); + for (typename vector::iterator node = nodes.begin(); node != nodes.end(); ++node) *node = new Node(); @@ -711,8 +767,7 @@ Graph::Graph(const Graph& graph) * ------------------------------------------------------------------------- */ { nodes.reserve(graph.nodes.size()); - for (typename vector::const_iterator - node = graph.nodes.begin(); + for (typename vector::const_iterator node = graph.nodes.begin(); node != graph.nodes.end(); ++node) nodes.push_back(new Node(**node)); } @@ -738,8 +793,7 @@ Graph& Graph::operator= clear(); nodes.reserve(graph.nodes.size()); - for (typename vector::const_iterator - node = graph.nodes.begin(); + for (typename vector::const_iterator node = graph.nodes.begin(); node != graph.nodes.end(); ++node) nodes.push_back(new Node(**node)); @@ -782,8 +836,7 @@ void Graph::clear() * * ------------------------------------------------------------------------- */ { - for (typename vector::reverse_iterator - node = nodes.rbegin(); + for (typename vector::reverse_iterator node = nodes.rbegin(); node != nodes.rend(); ++node) delete *node; @@ -924,13 +977,12 @@ Graph::stats() const * * ------------------------------------------------------------------------- */ { - pair::size_type, unsigned long int> result; + pair result; result.first = nodes.size(); result.second = 0; - for (typename vector::const_iterator - node = nodes.begin(); + for (typename vector::const_iterator node = nodes.begin(); node != nodes.end(); ++node) result.second += (*node)->edges().size(); @@ -964,7 +1016,7 @@ Graph::subgraphStats(const size_type index) const if (index >= s) throw NodeIndexException(); - stack > unprocessed_nodes; + stack > unprocessed_nodes; BitArray visited_nodes(s); visited_nodes.clear(s); @@ -1468,6 +1520,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. @@ -1574,8 +1748,7 @@ inline void EdgeList::insert(Graph::Edge* edge) } /* ========================================================================= */ -inline list::Edge*, ALLOC(Graph::Edge*) > - ::const_iterator +inline list::Edge*>::const_iterator EdgeList::find(const Graph::Edge* edge) const /* ---------------------------------------------------------------------------- * @@ -1586,9 +1759,9 @@ EdgeList::find(const Graph::Edge* edge) const * between the actual values of the edges (not the * pointers). * - * Returns: A list::Edge*, ALLOC>::const_iterator + * Returns: A list::Edge*>::const_iterator * pointing to the edge in the list or - * list::Edge*, ALLOC>::end() if the edge is + * list::Edge*>::end() if the edge is * not found in the list. * * ------------------------------------------------------------------------- */ @@ -1603,7 +1776,7 @@ EdgeList::find(const Graph::Edge* edge) const } /* ========================================================================= */ -inline list::Edge*, ALLOC(Graph::Edge*) >::iterator +inline list::Edge*>::iterator EdgeList::find(const Graph::Edge* edge) /* ---------------------------------------------------------------------------- * @@ -1614,9 +1787,9 @@ EdgeList::find(const Graph::Edge* edge) * between the actual values of the edges (not the * pointers). * - * Returns: A list::Edge*, ALLOC>::iterator pointing + * Returns: A list::Edge*>::iterator pointing * to the edge in the list or - * list::Edge*, ALLOC>::end() if the edge is + * list::Edge*>::end() if the edge is * not found in the list. * * ------------------------------------------------------------------------- */ @@ -1685,8 +1858,7 @@ inline void EdgeSlist::insert(Graph::Edge* edge) } /* ========================================================================= */ -inline slist::Edge*, ALLOC(Graph::Edge*) > - ::const_iterator +inline slist::Edge*>::const_iterator EdgeSlist::find(const Graph::Edge* edge) const /* ---------------------------------------------------------------------------- * @@ -1697,9 +1869,9 @@ EdgeSlist::find(const Graph::Edge* edge) const * between the actual values of the edges (not the * pointers). * - * Returns: A slist::Edge*, ALLOC>::const_iterator + * Returns: A slist::Edge*>::const_iterator * pointing to the edge in the list or - * slist::Edge*, ALLOC>::end() if the edge + * slist::Edge*>::end() if the edge * is not found in the list. * * ------------------------------------------------------------------------- */ @@ -1714,8 +1886,7 @@ EdgeSlist::find(const Graph::Edge* edge) const } /* ========================================================================= */ -inline slist::Edge*, ALLOC(Graph::Edge*) > - ::iterator +inline slist::Edge*>::iterator EdgeSlist::find(const Graph::Edge* edge) /* ---------------------------------------------------------------------------- * @@ -1726,9 +1897,9 @@ EdgeSlist::find(const Graph::Edge* edge) * between the actual values of the edges (not the * pointers). * - * Returns: A slist::Edge*, ALLOC>::iterator + * Returns: A slist::Edge*>::iterator * pointing to the edge in the list or - * slist::Edge*, ALLOC>::end() if the edge + * slist::Edge*>::end() if the edge * is not found in the list. * * ------------------------------------------------------------------------- */ @@ -1797,8 +1968,7 @@ inline void EdgeVector::insert(Graph::Edge* edge) } /* ========================================================================= */ -inline vector::Edge*, ALLOC(Graph::Edge*) > - ::const_iterator +inline vector::Edge*>::const_iterator EdgeVector::find(const Graph::Edge* edge) const /* ---------------------------------------------------------------------------- * @@ -1809,9 +1979,9 @@ EdgeVector::find(const Graph::Edge* edge) const * between the actual values of the edges (not the * pointers). * - * Returns: A vector::Edge*, ALLOC>::const_iterator + * Returns: A vector::Edge*>::const_iterator * pointing to the edge in the container or - * vector::Edge*, ALLOC>::end() if the + * vector::Edge*>::end() if the * edge is not found in the container. * * ------------------------------------------------------------------------- */ @@ -1826,8 +1996,7 @@ EdgeVector::find(const Graph::Edge* edge) const } /* ========================================================================= */ -inline vector::Edge*, ALLOC(Graph::Edge*) > - ::iterator +inline vector::Edge*>::iterator EdgeVector::find(const Graph::Edge* edge) /* ---------------------------------------------------------------------------- * @@ -1838,9 +2007,9 @@ EdgeVector::find(const Graph::Edge* edge) * between the actual values of the edges (not the * pointers). * - * Returns: A vector::Edge*, ALLOC>::iterator + * Returns: A vector::Edge*>::iterator * pointing to the edge in the container or - * vector::Edge*, ALLOC>::end() if the edge + * vector::Edge*>::end() if the edge * is not found in the container. * * ------------------------------------------------------------------------- */ diff --git a/lbtt/src/IntervalList.cc b/lbtt/src/IntervalList.cc index 52a5ebb35..d35c9b66e 100644 --- a/lbtt/src/IntervalList.cc +++ b/lbtt/src/IntervalList.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 2004 - * Heikki Tauriainen + * Copyright (C) 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -41,7 +41,7 @@ void IntervalList::merge(unsigned long int min, unsigned long int max) if (min > max) return; - list::iterator interval; + list::iterator interval; for (interval = intervals.begin(); interval != intervals.end() && interval->second + 1 < min; ++interval) @@ -68,14 +68,14 @@ void IntervalList::merge(unsigned long int min, unsigned long int max) if (interval->second < max) { interval->second = max; - list::iterator interval2 = interval; + 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; + list::iterator interval_to_erase = interval2; ++interval2; intervals.erase(interval_to_erase); } @@ -97,7 +97,7 @@ void IntervalList::remove(unsigned long int min, unsigned long int max) if (min > max) return; - list::iterator interval; + list::iterator interval; for (interval = intervals.begin(); interval != intervals.end() && interval->second < min; ++interval) @@ -126,7 +126,7 @@ void IntervalList::remove(unsigned long int min, unsigned long int max) } else /* min <= imin <= imax <= max */ { - list::iterator interval_to_erase = interval; + list::iterator interval_to_erase = interval; ++interval; intervals.erase(interval_to_erase); } @@ -148,7 +148,7 @@ bool IntervalList::covers(unsigned long int min, unsigned long int max) const if (min > max) return true; /* empty interval is always covered */ - list::const_iterator interval; + list::const_iterator interval; for (interval = intervals.begin(); interval != intervals.end() && min > interval->second; ++interval) @@ -173,8 +173,7 @@ string IntervalList::toString() const * ------------------------------------------------------------------------- */ { string s; - for (list::const_iterator - interval = intervals.begin(); + for (list::const_iterator interval = intervals.begin(); interval != intervals.end(); ++interval) { diff --git a/lbtt/src/IntervalList.h b/lbtt/src/IntervalList.h index 2a55b9641..b8276bf34 100644 --- a/lbtt/src/IntervalList.h +++ b/lbtt/src/IntervalList.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 2004 - * Heikki Tauriainen + * Copyright (C) 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -74,13 +74,15 @@ public: unsigned long int operator++(int); /* Postfix increment. */ private: - const list* /* The interval list */ - interval_list; /* associated with the */ - /* iterator. */ + const list* interval_list; /* The interval list + * associated with the + * iterator. + */ - list /* An iterator pointing */ - ::const_iterator interval; /* at the current */ - /* interval list. */ + list::const_iterator interval; /* An iterator pointing at + * the current intrerval + * list. + */ unsigned long int element; /* Element currently * pointed to by the @@ -143,8 +145,7 @@ public: * iterators. */ - typedef list /* Size type. */ - ::size_type size_type; + typedef list::size_type size_type; /* Size type. */ size_type size() const; /* Tell the number of * disjoint intervals in @@ -170,7 +171,7 @@ public: */ private: - list intervals; /* List of intervals. */ + list intervals; /* List of intervals. */ friend class const_iterator; }; diff --git a/lbtt/src/LbtWrapper.h b/lbtt/src/LbtWrapper.h index 70319c2da..bd889b18d 100644 --- a/lbtt/src/LbtWrapper.h +++ b/lbtt/src/LbtWrapper.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lbtt/src/LbttAlloc.h b/lbtt/src/LbttAlloc.h index b4019d922..c98ceaa07 100644 --- a/lbtt/src/LbttAlloc.h +++ b/lbtt/src/LbttAlloc.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -22,12 +22,6 @@ #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 /* GNU libc 2.3.2's copy of obstack.h uses a definition of __INT_TO_PTR diff --git a/lbtt/src/Ltl-parse.yy b/lbtt/src/Ltl-parse.yy index d558db0b7..805d5965b 100644 --- a/lbtt/src/Ltl-parse.yy +++ b/lbtt/src/Ltl-parse.yy @@ -1,6 +1,6 @@ /* - * Copyright (C) 2004 - * Heikki Tauriainen + * Copyright (C) 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -44,9 +44,9 @@ static LtlFormula* result; /* This variable stores the * ltl_parse. */ -static std::set, /* Intermediate results. */ - ALLOC(LtlFormula*) > /* (This set is used */ - intermediate_results; /* for keeping track of +static std::set intermediate_results; /* Intermediate results. + * (This set is used + * for keeping track of * the subformulas of a * partially constructed * formula in case the @@ -426,8 +426,8 @@ LtlFormula* parseFormula(istream& stream) } catch (...) { - for (std::set, ALLOC(LtlFormula*) > - ::const_iterator f = intermediate_results.begin(); + for (std::set::const_iterator + f = intermediate_results.begin(); f != intermediate_results.end(); ++f) LtlFormula::destruct(*f); diff --git a/lbtt/src/LtlFormula.cc b/lbtt/src/LtlFormula.cc index 5d0aa0cef..faad76f56 100644 --- a/lbtt/src/LtlFormula.cc +++ b/lbtt/src/LtlFormula.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,10 +17,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #include "FormulaWriter.h" #include "LtlFormula.h" @@ -28,10 +24,8 @@ namespace Ltl { -map - LtlFormula::formula_storage; +set /* Shared storage for */ + LtlFormula::formula_storage; /* LTL formulae. */ unsigned long int /* Upper limit for the */ LtlFormula::eval_proposition_id_limit; /* atomic proposition @@ -42,8 +36,6 @@ unsigned long int /* Upper limit for the */ * truth assignment). */ - - /****************************************************************************** * * Function for obtaining the infix symbol associated with a given @@ -147,12 +139,10 @@ public: */ private: - stack > + stack > formula_stack; - stack > - negation_stack; + stack > negation_stack; NnfConverter(const NnfConverter&); /* Prevent copying and */ NnfConverter& operator=(const NnfConverter&); /* assignment of @@ -203,8 +193,7 @@ class SubformulaCollector public: SubformulaCollector /* Constructor. */ (stack >& + deque >& result_stack); ~SubformulaCollector(); /* Destructor. */ @@ -216,8 +205,7 @@ public: private: stack >& + deque >& subformula_stack; SubformulaCollector(const SubformulaCollector&); /* Prevent copying and */ @@ -389,9 +377,7 @@ inline void FormulaSizeCounter::operator()(const LtlFormula*, int) /* ========================================================================= */ inline SubformulaCollector::SubformulaCollector - (stack >& - result_stack) : + (stack >& result_stack) : subformula_stack(result_stack) /* ---------------------------------------------------------------------------- * @@ -773,9 +759,7 @@ unsigned long int LtlFormula::size() const /* ========================================================================= */ void LtlFormula::collectSubformulae - (stack >& - result_stack) const + (stack >& result_stack) const /* ---------------------------------------------------------------------------- * * Description: Collects the subformulae of a LtlFormula into a stack. After @@ -879,193 +863,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..a53303ba7 100644 --- a/lbtt/src/LtlFormula.h +++ b/lbtt/src/LtlFormula.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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. @@ -264,21 +261,18 @@ private: void collectSubformulae /* Builds a stack of the */ (stack >& + deque >& /* formula. */ result_stack) const; typedef pair /* Shorthand type */ FormulaStackElement; /* definitions for the */ typedef stack >/* checking algorithm. */ - FormulaStack; + deque > /* satisfiability */ + FormulaStack; /* checking algorithm. */ typedef pair TableauStackElement; typedef stack > + deque > TableauStack; bool sat_eval /* Helper function for */ @@ -288,9 +282,8 @@ private: * formula. */ - static map /* LTL formulae. */ - formula_storage; + static set /* Shared storage for */ + formula_storage; /* LTL formulae. */ static unsigned long int /* Upper limit for the */ eval_proposition_id_limit; /* atomic proposition @@ -330,6 +323,16 @@ private: +/****************************************************************************** + * + * Interface to the formula parser. + * + *****************************************************************************/ + +extern LtlFormula* parseFormula(istream& stream); + + + /****************************************************************************** * * A class for atomic propositions. @@ -474,7 +477,7 @@ private: * to satisfy the * LtlFormula member * function interface. - */ + */ }; @@ -1076,7 +1079,7 @@ typedef BinaryFormula Before; *****************************************************************************/ /* ========================================================================= */ -inline LtlFormula::LtlFormula() +inline LtlFormula::LtlFormula() : refcount(1) /* ---------------------------------------------------------------------------- * * Description: Constructor for class LtlFormula. Initializes the attributes @@ -1088,8 +1091,6 @@ inline LtlFormula::LtlFormula() * * --------------------------------------------------------------------------*/ { - info_flags.is_propositional = 0; - info_flags.is_constant = 0; } /* ========================================================================= */ @@ -1122,14 +1123,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 +1143,7 @@ inline LtlFormula* LtlFormula::clone() * * ------------------------------------------------------------------------- */ { - formula_storage.find(this)->second++; + ++refcount; return this; } @@ -1241,8 +1237,7 @@ inline LtlFormula* LtlFormula::read(istream& stream) * * ------------------------------------------------------------------------- */ { - Exceptional_istream estream(&stream, ios::badbit | ios::failbit); - return read(estream); + return parseFormula(stream); } /* ========================================================================= */ @@ -1303,8 +1298,6 @@ inline Exceptional_ostream& operator<< return stream; } - - /* ========================================================================= */ inline LtlFormula& LtlFormula::insertToStorage(LtlFormula* f) /* ---------------------------------------------------------------------------- @@ -1317,19 +1310,15 @@ 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 +1560,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 +1874,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 +2176,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/NeverClaim-lex.ll b/lbtt/src/NeverClaim-lex.ll index 8123ee3a8..54f8d3cb9 100644 --- a/lbtt/src/NeverClaim-lex.ll +++ b/lbtt/src/NeverClaim-lex.ll @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -30,6 +30,7 @@ extern int current_neverclaim_line_number; %option never-interactive %option noyywrap +%option nounput %% diff --git a/lbtt/src/NeverClaim-parse.yy b/lbtt/src/NeverClaim-parse.yy index 8688dd00a..bdbdfb4da 100644 --- a/lbtt/src/NeverClaim-parse.yy +++ b/lbtt/src/NeverClaim-parse.yy @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lbtt/src/NeverClaimAutomaton.cc b/lbtt/src/NeverClaimAutomaton.cc index 7f6ad3592..122f389b7 100644 --- a/lbtt/src/NeverClaimAutomaton.cc +++ b/lbtt/src/NeverClaimAutomaton.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,10 +17,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #include #include @@ -66,8 +62,7 @@ void NeverClaimAutomaton::clear() * * ------------------------------------------------------------------------- */ { - for (vector::iterator - state = state_list.begin(); + for (vector::iterator state = state_list.begin(); state != state_list.end(); ++state) delete (*state); @@ -158,8 +153,7 @@ void NeverClaimAutomaton::write(const char* output_filename) * `-1'. */ - for (vector::const_iterator - state = state_list.begin(); + for (vector::const_iterator state = state_list.begin(); state != state_list.end(); ++state) { @@ -167,7 +161,7 @@ void NeverClaimAutomaton::write(const char* output_filename) + ((*state)->initial() ? "1" : "0") + ' ' + ((*state)->accepting() ? "0 " : "") + "-1\n"; - for (multimap, ALLOC(Cstr*) >::const_iterator + for (multimap::const_iterator transition = (*state)->transitions().begin(); transition != (*state)->transitions().end(); ++transition) { @@ -272,7 +266,7 @@ NeverClaimAutomaton::StateInfo::~StateInfo() * * ------------------------------------------------------------------------- */ { - for (multimap, ALLOC(Cstr*) >::const_iterator + for (multimap::const_iterator transition = state_transitions.begin(); transition != state_transitions.end(); ++transition) @@ -312,7 +306,7 @@ ParseErrorException::ParseErrorException else { string space_string(msg.substr(0, error_pos)); - for (string::size_type c = 0; c < msg.length(); c++) + for (string::size_type c = 0; c < space_string.length(); c++) if (space_string[c] != ' ' && space_string[c] != '\t') space_string[c] = ' '; diff --git a/lbtt/src/NeverClaimAutomaton.h b/lbtt/src/NeverClaimAutomaton.h index 0ab31b905..49cf37324 100644 --- a/lbtt/src/NeverClaimAutomaton.h +++ b/lbtt/src/NeverClaimAutomaton.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -93,12 +93,13 @@ public: */ private: - vector /* States of the */ - state_list; /* automaton. */ + vector state_list; /* States of the automaton. + */ - map, /* Mapping from state */ - ALLOC(StateInfo*) > /* labels to the states */ - label_mapping; /* itself. */ + map label_mapping; /* Mapping from state + * labels to the states + * itself. + */ StateInfo* current_state; /* Pointer to the state * introduced most recently @@ -142,9 +143,9 @@ public: /* of the state. */ - const multimap, /* Returns the labels of */ - ALLOC(Cstr*) >& /* the state's successor */ - transitions() const; /* states, including the + const multimap& transitions() const; /* Returns the labels of + * the state's successor + * states, including the * conditions controlling * the enabledness of the * transition. @@ -174,8 +175,8 @@ private: * accepting state? */ - multimap, ALLOC(Cstr*) > /* Labels of the state's */ - state_transitions; /* successors, including + multimap state_transitions; /* Labels of the state's + * successors, including * the guard formulae * controlling the * enabledness of the @@ -373,9 +374,7 @@ inline bool& NeverClaimAutomaton::StateInfo::accepting() } /* ========================================================================= */ -inline const multimap, - ALLOC(NeverClaimAutomaton::Cstr*) >& +inline const multimap& NeverClaimAutomaton::StateInfo::transitions() const /* ---------------------------------------------------------------------------- * diff --git a/lbtt/src/PathEvaluator.cc b/lbtt/src/PathEvaluator.cc index 84b769974..7737ae251 100644 --- a/lbtt/src/PathEvaluator.cc +++ b/lbtt/src/PathEvaluator.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,10 +17,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #include #include @@ -53,9 +49,8 @@ void PathEvaluator::reset() current_loop_state = 0; path_states.clear(); - for (map::iterator it - = eval_info.begin(); + for (map::iterator + it = eval_info.begin(); it != eval_info.end(); ++it) delete it->second; @@ -65,26 +60,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. * @@ -92,13 +86,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(); } @@ -128,8 +130,7 @@ bool PathEvaluator::evaluate current_formula = &formula; current_path = &statespace; - map, ALLOC(StateSpace::size_type) > ordering; + map ordering; StateSpace::size_type state = statespace.initialState(); StateSpace::size_type state_count = 0; @@ -173,9 +174,7 @@ bool PathEvaluator::eval() * * ------------------------------------------------------------------------- */ { - stack > - subformula_stack; + stack > subformula_stack; const LtlFormula* f; BitArray* val; diff --git a/lbtt/src/PathEvaluator.h b/lbtt/src/PathEvaluator.h index 7b7a178ef..4d361d8f8 100644 --- a/lbtt/src/PathEvaluator.h +++ b/lbtt/src/PathEvaluator.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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, @@ -105,15 +105,15 @@ private: * the current path. */ - vector /* between states of the */ - path_states; /* path and the states + vector path_states; /* Correspondence + * between states of the + * path and the states * of the current state * space. */ map /* truth values of the */ + LtlFormula::ptr_less> /* truth values of the */ eval_info; /* subformulae of the * formula to be * evaluated. diff --git a/lbtt/src/PathIterator.cc b/lbtt/src/PathIterator.cc index 3f468940a..6489b7b1b 100644 --- a/lbtt/src/PathIterator.cc +++ b/lbtt/src/PathIterator.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,10 +17,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #include "BitArray.h" #include "PathIterator.h" diff --git a/lbtt/src/PathIterator.h b/lbtt/src/PathIterator.h index 8c6f914ca..28e02a2f5 100644 --- a/lbtt/src/PathIterator.h +++ b/lbtt/src/PathIterator.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,10 +20,6 @@ #ifndef PATHITERATOR_H #define PATHITERATOR_H -#ifdef __GNUC__ -#pragma interface -#endif /* __GNUC__ */ - #include #include "StateSpace.h" diff --git a/lbtt/src/Product.h b/lbtt/src/Product.h index f7b49a677..041ff5d36 100644 --- a/lbtt/src/Product.h +++ b/lbtt/src/Product.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -393,8 +393,8 @@ public: typedef ProductEdgeCollection EdgeContainerType; /* required for making */ /* Product */ struct PathElement; /* suitable for */ - typedef deque /* instantiating the */ - Path; /* SccCollection + typedef deque Path; /* instantiating the + * SccCollection * template (see * SccCollection.h). */ @@ -419,9 +419,7 @@ private: 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) >& + const map& /* product. */ predecessor) const; Operations operations; /* Operations for @@ -524,8 +522,8 @@ public: */ private: - const Graph::Edge* edge_1; /* Components of the */ - const Graph::Edge* edge_2; /* transition. */ + GraphEdgeContainer::const_iterator edge_1; /* Components of the */ + GraphEdgeContainer::const_iterator edge_2; /* transition. */ }; @@ -655,9 +653,10 @@ protected: * acceptance sets. */ - typedef deque /* the above */ - AcceptanceStack; /* associations. */ + typedef deque /* Stack formed from */ + AcceptanceStack; /* the above + * associations. + */ AcceptanceStack acceptance_stack; /* Stack for storing the * dfs numbers of roots @@ -877,9 +876,9 @@ private: * reachable. */ - set, /* Set of states from */ - ALLOC(size_type) > /* which an accepting */ - reachability_info; /* component is known to + set reachability_info; /* Set of states from + * which an accepting + * component is known to * be reachable in the * product. */ @@ -928,9 +927,9 @@ public: ~AcceptingComponentFinder(); /* Destructor. */ - typedef set, /* Type definition for */ - ALLOC(size_type) > /* the set of product */ - SccType; /* state identifiers in + typedef set SccType; /* Type definition for + * the set of product + * state identifiers in * an accepting * strongly connected * component. @@ -1369,10 +1368,9 @@ void Product::findWitness 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; + deque search_queue; + set visited; + map shortest_path_predecessor; size_type bfs_root = search_start_state; @@ -1475,8 +1473,7 @@ 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 + const map& predecessor) const /* ---------------------------------------------------------------------------- * * Description: Helper function for constructing a segment of an accepting @@ -1598,7 +1595,7 @@ template inline Product::ProductEdge::ProductEdge (const GraphEdgeContainer::const_iterator& e1, const GraphEdgeContainer::const_iterator& e2) - : edge_1(*e1), edge_2(*e2) + : edge_1(e1), edge_2(e2) /* ---------------------------------------------------------------------------- * * Description: Constructor for class Product::ProductEdge. @@ -1643,7 +1640,7 @@ Product::ProductEdge::firstComponent() const * * ------------------------------------------------------------------------- */ { - return *edge_1; + return **edge_1; } /* ========================================================================= */ @@ -1662,7 +1659,7 @@ Product::ProductEdge::secondComponent() const * * ------------------------------------------------------------------------- */ { - return *edge_2; + return **edge_2; } /* ========================================================================= */ @@ -1679,7 +1676,7 @@ Product::ProductEdge::targetNode() const * * ------------------------------------------------------------------------- */ { - return product->stateId(edge_1->targetNode(), edge_2->targetNode()); + return product->stateId((*edge_1)->targetNode(), (*edge_2)->targetNode()); } diff --git a/lbtt/src/ProductAutomaton.cc b/lbtt/src/ProductAutomaton.cc new file mode 100644 index 000000000..c733e57d7 --- /dev/null +++ b/lbtt/src/ProductAutomaton.cc @@ -0,0 +1,1063 @@ +/* + * 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 +#include "ProductAutomaton.h" +#include "SccIterator.h" + +namespace Graph +{ + +/****************************************************************************** + * + * Function definitions for class ProductAutomaton. + * + *****************************************************************************/ + +/* ========================================================================= */ +void ProductAutomaton::clear() +/* ---------------------------------------------------------------------------- + * + * Description: Makes the automaton empty. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + buchi_automaton = 0; + statespace_size = 0; + +#ifdef HAVE_OBSTACK_H + for (vector::iterator state = nodes.begin(); + state != nodes.end(); + ++state) + static_cast(*state)->~ProductState(); + + if (!nodes.empty()) + { + store.free(*nodes.begin()); + nodes.clear(); + nodes.reserve(0); + } + +#endif /* HAVE_OBSTACK_H */ + + Graph::clear(); +} + +/* ========================================================================= */ +ProductAutomaton::size_type ProductAutomaton::expand(size_type node_count) +/* ---------------------------------------------------------------------------- + * + * Description: Inserts states to a ProductAutomaton. + * + * Argument: node_count -- Number of states to be inserted. + * + * Returns: The index of the last state inserted. + * + * ------------------------------------------------------------------------- */ +{ + nodes.reserve(nodes.size() + node_count); + + while (node_count > 0) + { +#ifdef HAVE_OBSTACK_H + void* state_storage = store.alloc(sizeof(ProductState)); + ProductState* new_product_state = new(state_storage) ProductState(); +#else + ProductState* new_product_state = new ProductState(); +#endif /* HAVE_OBSTACK_H */ + + try + { + nodes.push_back(new_product_state); + } + catch (...) + { +#ifdef HAVE_OBSTACK_H + new_product_state->~ProductState(); + store.free(state_storage); +#else + delete new_product_state; +#endif /* HAVE_OBSTACK_H */ + throw; + } + node_count--; + } + + return size() - 1; +} + +/* ========================================================================= */ +void ProductAutomaton::connect(const size_type father, const size_type child) +/* ---------------------------------------------------------------------------- + * + * Description: Connects two states of the product automaton. + * + * Arguments: father, child -- State identifiers. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + Edge* edge = operator[](child).incoming_edge; + + if (edge != 0) + { + nodes[father]->outgoing_edges.insert(edge); + return; + } + +#ifdef HAVE_OBSTACK_H + void* edge_storage = store.alloc(sizeof(Edge)); + edge = new(edge_storage) Edge(child); +#else + edge = new Edge(child); +#endif /* HAVE_OBSTACK_H */ + + try + { + nodes[father]->outgoing_edges.insert(edge); + } + catch (...) + { +#ifdef HAVE_OBSTACK_H + edge->~Edge(); + store.free(edge_storage); +#else + delete edge; +#endif /* HAVE_OBSTACK_H */ + throw; + } + + operator[](child).incoming_edge = edge; +} + +/* ========================================================================= */ +void ProductAutomaton::disconnect + (const size_type father, const size_type child) +/* ---------------------------------------------------------------------------- + * + * Description: Disconnects two states of the product automaton. + * + * Arguments: father, child -- Identifiers for two states. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + Edge e(child); + + /* + * Scan the set of `father''s outgoing transitions for a transition to the + * given target state and remove it if such a transition exists. + */ + + GraphEdgeContainer::iterator search_edge + = nodes[father]->outgoing_edges.find(&e); + + if (search_edge != nodes[father]->outgoing_edges.end()) + nodes[father]->outgoing_edges.erase(search_edge); +} + +/* ========================================================================= */ +void ProductAutomaton::print + (ostream& stream, const int indent, const GraphOutputFormat) const +/* ---------------------------------------------------------------------------- + * + * Description: Writes information about a product automaton to a stream. + * + * Arguments: stream -- A reference to an output stream. + * indent -- Number of spaces to leave to the left of output. + * + * The third (dummy) parameter is needed to support the + * function interface defined in the base class. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); + + if (nodes.empty()) + estream << string(indent, ' ') + "The product automaton is empty.\n"; + else + { + pair statistics = stats(); + bool first_printed; + + estream << string(indent, ' ') + "The product automaton consists of\n" + + string(indent + 4, ' ') + << statistics.first + << " states and\n" + string(indent + 4, ' ') + << statistics.second + << " transitions.\n"; + + size_type s = nodes.size(); + for (size_type state = 0; state < s; ++state) + { + estream << string(indent, ' ') + "State " << state << ":\n" + + string(indent + 4, ' ') + "Automaton state: " + << buchiState(state) + << " (acceptance sets: {"; + + for (unsigned long int acceptance_set = 0; + acceptance_set < buchi_automaton->numberOfAcceptanceSets(); + acceptance_set++) + { + if ((*buchi_automaton)[buchiState(state)].acceptanceSets(). + test(acceptance_set)) + { + if (first_printed) + estream << ", "; + else + first_printed = true; + estream << acceptance_set; + } + } + + estream << "}\n" + string(indent + 4, ' ') + "System state: " + << systemState(state) + << '\n'; + + operator[](state).print(stream, indent + 4); + } + } + + estream.flush(); +} + +/* ========================================================================= */ +void ProductAutomaton::computeProduct + (const BuchiAutomaton& automaton, const StateSpace& statespace, + const bool global_product) +/* ---------------------------------------------------------------------------- + * + * Description: Initializes the synchronous product of a Büchi automaton and + * a state space. + * + * Arguments: automaton -- A reference to a constant BuchiAutomaton. + * statespace -- A reference to a constant StateSpace. + * global_product -- Controls whether the synchronous product + * of the automaton and the statespace is + * computed `globally' (i.e., with respect + * to all states in the state space) or + * `locally' (i.e., with respect only to the + * initial state of the state space). + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + clear(); + + buchi_automaton = &automaton; + statespace_size = statespace.size(); + + /* + * If either the Büchi automaton or the state space is empty, their product + * is also empty. + */ + + if (automaton.empty() || statespace.empty()) + return; + + /* + * If the (worst-case) product of the size of the automaton and the state + * space size exceeds the maximum size of a product automaton, throw an + * exception (if this holds, the simple product state hashing technique + * used below will not work correctly). + */ + + if (automaton.size() > (nodes.max_size() / statespace.size())) + throw ProductSizeException(); + + /* + * Product states will be numerically encoded using the equation + * + * #P = #S + |S| * #B + * + * where + * |S| is the number of states in the state space, + * #P is an identifier of a product state, + * #S is an identifier of a state in the state space (0...|S|-1), + * #B is an identifier of a state in the Büchi automaton (0...|B|-1) + * and + * + * From this encoding we obtain + * #S = #P % |S|, and + * #B = #P / |S|. + */ + + map, ALLOC(size_type) > + product_state_mapping; + + /* + * Initialize the product automaton. If the product is to be computed + * globally, initialize the result with state pairs (q_0, s) where q_0 is + * the initial state of the Büchi automaton and s goes through all states + * in the state space. (These states will have the lowest indices in the + * product automaton, i.e. covering the index interval + * 0 ... [statespace.size() - 1] .) In the case of a local product, + * initialize the result with the single state (q_0, s_0), a pair consisting + * of the initial states of the two other structures. + * + * The final product automaton is obtained by computing the closure of this + * set of states under the product transition relation. For this purpose, + * a depth-first search will be used (with `mapping_stack' as the search + * stack; initially, it contains the same states as described above). + */ + + expand(global_product ? statespace.size() : 1); + + stack > mapping_stack; + + pair state_map_entry + = make_pair(automaton.initialState() * statespace_size, 0); + + for (state_map_entry.second = 0; + state_map_entry.second < (global_product ? statespace_size : 1); + ++state_map_entry.first, ++state_map_entry.second) + { + product_state_mapping.insert(state_map_entry); + mapping_stack.push(state_map_entry.first); + + operator[](state_map_entry.second).hashValue() = state_map_entry.first; + } + + /* + * Compute the product automaton by using a depth-first search. + */ + + const GraphEdgeContainer* automaton_transitions; + const GraphEdgeContainer* system_transitions; + + BuchiAutomaton::size_type current_automaton_state; + StateSpace::size_type current_system_state; + size_type current_product_state; + bool current_product_state_valid; + size_type current_mapping; + + try + { + while (!mapping_stack.empty()) + { + if (::user_break) + throw UserBreakException(); + + /* + * Pop a state mapping off the stack. + */ + + current_product_state_valid = false; + current_mapping = mapping_stack.top(); + mapping_stack.pop(); + + current_automaton_state = current_mapping / statespace_size; + current_system_state = current_mapping % statespace_size; + + /* + * Go through the transitions of the original Büchi automaton. For all + * transitions enabled in the current state of the state space: + * + * - Compute all product states that can be reached by firing the + * transition. These are the states (q', s') where q' is the + * state of the automaton after firing the transition and s' + * is a successor of the current state of the system. + * + * - If there is no corresponding product state for the state pair + * (q', s'), create a new product state and insert it into the + * product automaton. Push the mapping also on the stack so that + * the newly created product state will be eventually processed. + * + * - Connect the target product state to the current product state + * (in effect, storing information only about the _predecessors_ of + * each product state). This will result in a product space in + * which all edges are reversed. However, this is all that is + * needed since no information is needed about the successors of + * any product state when searching for the accepting cycles in the + * product graph (this will be performed using a backward search, + * see the functions `emptinessCheck' and + * `findAcceptingExecution'). + */ + + automaton_transitions = &automaton[current_automaton_state].edges(); + + for (GraphEdgeContainer::const_iterator + automaton_transition = automaton_transitions->begin(); + automaton_transition != automaton_transitions->end(); + ++automaton_transition) + { + if ((static_cast + (*automaton_transition))-> + enabled(statespace[current_system_state].positiveAtoms(), + statespace.numberOfPropositions())) + { + if (!current_product_state_valid) + { + current_product_state = product_state_mapping[current_mapping]; + current_product_state_valid = true; + system_transitions = &statespace[current_system_state].edges(); + } + + pair, ALLOC(size_type) > + ::iterator, + bool> + check_state; + + for (GraphEdgeContainer::const_iterator + system_transition = system_transitions->begin(); + system_transition != system_transitions->end(); + ++system_transition) + { + /* + * Compute a hash value for the target product state and test + * whether it has already been included in the product. + */ + + state_map_entry.first = + (*system_transition)->targetNode() + + statespace_size * (*automaton_transition)->targetNode(); + + check_state = product_state_mapping.insert(state_map_entry); + + if (check_state.second) /* insertion occurred */ + { + /* + * Create a new product state and adjust its hash value. + * `state_map_entry.second' holds the next free identifier for a + * new product state. + */ + + expand(); + + operator[](state_map_entry.second).hashValue() + = state_map_entry.first; + + connect(state_map_entry.second, current_product_state); + state_map_entry.second++; + + mapping_stack.push(state_map_entry.first); + } + else + { + size_type existing_state = (check_state.first)->second; + connect(existing_state, current_product_state); + } + } + } + } + } + } + catch (...) + { + clear(); + throw; + } +} + +/* ========================================================================= */ +void ProductAutomaton::emptinessCheck(Bitset& result) const +/* ---------------------------------------------------------------------------- + * + * Description: Performs an emptiness check on the product automaton, i.e. + * finds the set of system states starting an execution + * sequence accepted by the Büchi automaton. + * + * Argument: result -- A reference to the Bitset in which the result + * is stored. The Bitset must have enough space for + * as many states as there are in the state space. + * A `0' bit in some position n in the result means + * that no accepting executions begin from system + * state n; a `1' bit means the opposite. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + BitArray visited(nodes.size()); + visited.clear(nodes.size()); + + result.clear(); + + /* + * Scan the maximal strongly connected components of the product space to + * find any fair MSCCs (nontrivial MSCCs containing an accepting cycle of + * the Büchi automaton used for constructing the product). Note that + * although the product space contains only back edges (i.e., we are + * actually scanning the product space with all edges reversed), the + * reversal of the edges does not affect the MSCCs of the product space. + */ + + for (SccIterator + strongly_connected_component(*this); + !strongly_connected_component.atEnd(); + ++strongly_connected_component) + { + if (::user_break) + throw UserBreakException(); + + if (strongly_connected_component->fair(*this)) + { + /* + * Search for a previously unvisited state in the strongly connected + * component. + */ + + ProductScc::const_iterator st; + for (st = strongly_connected_component->begin(); + st != strongly_connected_component->end() && visited[*st]; + ++st) + ; + + if (st != strongly_connected_component->end()) + { + /* + * Starting from the unvisited state, perform a backward depth-first + * search to find all states (q_0, s) such that q_0 is the initial + * state of the automaton (the product states (q_0, s) are recognized + * by the product state numbering scheme, in which these states have + * the lowest indices). Add the corresponding system states to the + * result (these are the system states from which the nontrivial MSCC + * containing the accepting cycle can be reached). + */ + + size_type state = *st; + stack > + backward_search_stack; + const GraphEdgeContainer* predecessors; + + visited.setBit(state); + backward_search_stack.push(state); + + while (!backward_search_stack.empty()) + { + state = backward_search_stack.top(); + backward_search_stack.pop(); + + if (state < result.capacity() && state < statespace_size) + result.setBit(systemState(state)); + + predecessors = &(operator[](state).edges()); /* note that only back + * edges are stored in + * the product! */ + + for (GraphEdgeContainer::const_iterator predecessor + = predecessors->begin(); + predecessor != predecessors->end(); + ++predecessor) + { + if (!visited[(*predecessor)->targetNode()]) + { + backward_search_stack.push((*predecessor)->targetNode()); + visited.setBit((*predecessor)->targetNode()); + } + } + } + } + } + } +} + +/* ========================================================================= */ +void ProductAutomaton::findAcceptingExecution + (const StateSpace::size_type initial_state, + pair, + deque >& execution) const +/* ---------------------------------------------------------------------------- + * + * Description: Extracts an execution (beginning from a given system + * state) accepted by the Büchi automaton from the product + * space. The function behaves basically like + * ProductAutomaton::emptinessCheck, but it keeps additional + * information about the path of processed states during the + * backward search. This information is then used to extract + * the desired system execution from the graph. + * + * Arguments: initial_state -- Identifier of the system state. + * execution -- A reference to a pair of deques for + * storing the result. The `first' component + * of the pair represents an execution + * `prefix' (a sequence of + * + * pairs) leading from `initial_state' + * to an accepting cycle. The `second' + * component of the pair contains the cycle + * itself. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + BitArray visited(nodes.size()); + visited.clear(nodes.size()); + + deque& prefix = execution.first; + deque& cycle = execution.second; + + prefix.clear(); + cycle.clear(); + + /* + * Scan the non-trivial maximal strongly connected components of the + * product space to find the fair MSCCs (non-trivial MSCCs containing an + * accepting cycle of the Büchi automaton used for constructing the + * product). Note that although the product space contains only back edges + * (i.e., we are actually scanning the product space with all edges + * reversed), the reversal of the edges does not affect the MSCCs of the + * product space. + */ + + for (SccIterator nmscc(*this); + !nmscc.atEnd(); + ++nmscc) + { + if (nmscc->fair(*this)) + { + const unsigned long int num_accept_sets + = buchi_automaton->numberOfAcceptanceSets(); + + /* + * Search the fair non-trivial maximal strongly connected component for + * a state belonging to some acceptance set of the Büchi automaton from + * which the product was constructed (or if the automaton has no + * acceptance sets, any state in the non-trivial MSCC). + */ + + ProductScc::const_iterator st = nmscc->begin(); + + if (buchi_automaton->numberOfAcceptanceSets() > 0) + { + while (st != nmscc->end()) + { + if ((*buchi_automaton)[buchiState(*st)].acceptanceSets(). + find(num_accept_sets) + < num_accept_sets) + break; + + ++st; + } + } + + if (st != nmscc->end()) + { + /* + * Try to find a (backward) path from the state in the MSCC back to + * the caller-given `initial state'. + */ + + size_type search_start_state = *st; + + if (search_start_state != initial_state) + { + typedef pair + BackwardSearchStackElement; + + deque + backward_search_stack; + + visited.clear(nodes.size()); + visited.setBit(search_start_state); + backward_search_stack.push_back + (make_pair(search_start_state, + operator[](search_start_state).edges().begin())); + + size_type predecessor; + + while (!backward_search_stack.empty()) + { + /* + * If it may be possible to find a shorter path to the initial + * state by still extending the current path (or if no path to + * the initial state has yet been found), scan through the + * predecessors of the `current' state (the last state inserted + * onto `backward_search_stack'). + */ + + while ((backward_search_stack.size() < prefix.size() + || prefix.empty()) + && backward_search_stack.back().second + != operator[](backward_search_stack.back().first) + .edges().end()) + { + predecessor + = (*backward_search_stack.back().second)->targetNode(); + + backward_search_stack.back().second++; + + /* + * If the given `initial state' is a predecessor of the current + * state, extract the path from the initial state to the state + * from where the backward search was started. + */ + + if (buchiState(predecessor) == buchi_automaton->initialState() + && systemState(predecessor) == initial_state) + { + prefix.clear(); + for (deque + ::const_iterator + state = backward_search_stack.begin(); + state != backward_search_stack.end(); + ++state) + { + prefix.push_front(make_pair(buchiState((*state).first), + systemState((*state).first))); + } + prefix.push_front(make_pair(buchiState(predecessor), + systemState(predecessor))); + } + + /* + * If some predecessor of the current state has not yet been + * visited, push it onto the backward search stack, then proceed + * with checking its predecessors. + */ + + else if (!visited[predecessor]) + { + visited.setBit(predecessor); + backward_search_stack.push_back + (make_pair(predecessor, + operator[](predecessor).edges().begin())); + } + } + + /* + * If all predecessors of a state have been processed, backtrack + * to the previous state on the path. + */ + + backward_search_stack.pop_back(); + } + } + + /* + * If a path was found from the `initial state' to the state in the + * MSCC, construct an accepting cycle by performing a breadth-first + * search in the MSCC. + */ + + if (!prefix.empty() || search_start_state == initial_state) + { + BitArray in_nmscc(nodes.size()); + in_nmscc.clear(nodes.size()); + + for (ProductScc::const_iterator state = nmscc->begin(); + state != nmscc->end(); + ++state) + in_nmscc.setBit(*state); + + BitArray collected_acc_sets; + collected_acc_sets.copy + ((*buchi_automaton)[buchiState(search_start_state)] + .acceptanceSets(), + num_accept_sets); + bool all_acceptance_sets_on_path + = (collected_acc_sets.count(num_accept_sets) == num_accept_sets); + + deque backward_search_queue; + map, ALLOC(size_type) > + shortest_path_predecessor; + + size_type bfs_root = search_start_state; + const GraphEdgeContainer* predecessors; + size_type state; + + visited.clear(nodes.size()); + visited.setBit(bfs_root); + backward_search_queue.push_back(bfs_root); + + bool cycle_finished = false; + + while (!cycle_finished) + { + predecessors = &operator[](backward_search_queue.front()).edges(); + + for (GraphEdgeContainer::const_iterator + predecessor = predecessors->begin(); + predecessor != predecessors->end(); + ++predecessor) + { + state = (*predecessor)->targetNode(); + + if (in_nmscc[state]) + { + /* + * If all acceptance sets have been collected and the search + * finds the first state of the cycle again, the cycle is + * complete. + */ + + if (all_acceptance_sets_on_path && state == search_start_state) + { + cycle_finished = true; + state = backward_search_queue.front(); + break; + } + else if (!visited[state]) + { + /* + * Update information about the breadth-first predecessor of + * an unvisited state. + */ + + shortest_path_predecessor[state] + = backward_search_queue.front(); + + /* + * If the unvisited state does not cover any `new' + * acceptance conditions, prepare to continue the search in + * that state by inserting the state into the search queue. + */ + + if (all_acceptance_sets_on_path + || (*buchi_automaton)[buchiState(state)].acceptanceSets() + .subset(collected_acc_sets, num_accept_sets)) + { + visited.setBit(state); + backward_search_queue.push_back(state); + } + + /* + * If the search finds an unvisited state which covers new + * acceptance sets, begin a new breadth-first search in + * that state. + */ + + else + { + all_acceptance_sets_on_path = true; + + for (unsigned long int accept_set = 0; + accept_set + < buchi_automaton->numberOfAcceptanceSets(); + accept_set++) + { + if ((*buchi_automaton)[buchiState(state)] + .acceptanceSets().test(accept_set)) + collected_acc_sets.setBit(accept_set); + else if (!collected_acc_sets.test(accept_set)) + all_acceptance_sets_on_path = false; + } + + deque cycle_fragment; + while (state != bfs_root) + { + cycle_fragment.push_back(make_pair(buchiState(state), + systemState(state))); + state = shortest_path_predecessor[state]; + } + cycle.insert(cycle.begin(), cycle_fragment.begin(), + cycle_fragment.end()); + + bfs_root = (*predecessor)->targetNode(); + visited.clear(nodes.size()); + visited.setBit(bfs_root); + backward_search_queue.clear(); + backward_search_queue.push_back(bfs_root); + backward_search_queue.push_back(bfs_root); + + break; + } + } + } + } + + backward_search_queue.pop_front(); + } + + deque cycle_fragment; + while (state != bfs_root) + { + cycle_fragment.push_back(make_pair(buchiState(state), + systemState(state))); + state = shortest_path_predecessor[state]; + } + + cycle.insert(cycle.begin(), cycle_fragment.begin(), + cycle_fragment.end()); + cycle.push_back(make_pair(buchiState(search_start_state), + systemState(search_start_state))); + + /* + * "Synchronize" the prefix of the witness execution with its cycle + * by removing from the end of the prefix the longest subsequence of + * states which occurs in the end of the cycle. The states in the + * cycle must be "rotated" accordingly to align the first state of + * the cycle correctly. + */ + + while (!prefix.empty() && prefix.back() == cycle.back()) + { + cycle.push_front(cycle.back()); + prefix.pop_back(); + cycle.pop_back(); + } + + return; + } + } + } + } + + /* + * The result will be empty if no accepting execution beginning from the + * given initial state could be found. + */ +} + + + +/****************************************************************************** + * + * Function definitions for class ProductAutomaton::ProductState. + * + *****************************************************************************/ + +/* ========================================================================= */ +void ProductAutomaton::ProductState::print + (ostream& stream, const int indent, const GraphOutputFormat) const +/* ---------------------------------------------------------------------------- + * + * Description: Writes information about the ProductState to a stream. + * + * Arguments: stream -- A reference to an output stream. + * indent -- Number of spaces to leave to the left of output. + * + * The third (dummy) argument is needed to support the function + * interface defined in the base class. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); + + if (edges().empty()) + estream << string(indent,' ') + "The product state has no predecessors.\n"; + else + { + bool first_printed = false; + estream << string(indent, ' ') + "Predecessor states: {"; + + for (GraphEdgeContainer::const_iterator predecessor = edges().begin(); + predecessor != edges().end(); ++predecessor) + { + if (first_printed) + estream << ", "; + else + first_printed = true; + estream << (*predecessor)->targetNode(); + } + estream << "}\n"; + } + + estream.flush(); +} + + + +/****************************************************************************** + * + * Function definitions for class ProductAutomaton::ProductScc. + * + *****************************************************************************/ + +/* ========================================================================= */ +bool ProductAutomaton::ProductScc::fair + (const ProductAutomaton& product_automaton) const +/* ---------------------------------------------------------------------------- + * + * Description: Tests whether a strongly connected component is fair in + * a product automaton. A strongly connected component is + * fair if and only if it is nontrivial (i.e. empty or + * containing a single state with a self-loop) and contains a + * state corresponding to a state from every acceptance set of + * the Büchi automaton used for constructing the given product. + * + * Arguments: product_automaton -- A constant reference to a + * ProductAutomaton. + * + * Returns: A truth value telling whether the strongly connected + * component is fair. + * + * ------------------------------------------------------------------------- */ +{ + /* + * A maximal strongly connected component is not fair if it is trivial. + */ + + if (empty() + || (size() == 1 && !product_automaton.connected(*begin(), *begin()))) + return false; + + /* + * Check whether the strongly connected component contains a state from each + * acceptance set of the Büchi automaton used for constructing the product + * (in this case, the component contains an accepting cycle of the + * automaton and is therefore fair). + */ + + const BuchiAutomaton* buchi_automaton = product_automaton.buchi_automaton; + const BitArray* acceptance_sets; + const unsigned long int number_of_acceptance_sets + = buchi_automaton->numberOfAcceptanceSets(); + + BitArray acceptance_sets_in_scc(number_of_acceptance_sets); + acceptance_sets_in_scc.clear(number_of_acceptance_sets); + + unsigned long int accept_set; + unsigned long int acceptance_set_counter = 0; + + for (const_iterator st = begin(); + st != end() && acceptance_set_counter < number_of_acceptance_sets; + ++st) + { + acceptance_sets = &(*buchi_automaton)[product_automaton.buchiState(*st)]. + acceptanceSets(); + + accept_set = acceptance_set_counter; + while (accept_set < number_of_acceptance_sets) + { + if (acceptance_sets->test(accept_set)) + { + acceptance_sets_in_scc.setBit(accept_set); + if (accept_set == acceptance_set_counter) + { + do + acceptance_set_counter++; + while (acceptance_set_counter < number_of_acceptance_sets + && acceptance_sets_in_scc[acceptance_set_counter]); + accept_set = acceptance_set_counter; + continue; + } + } + + accept_set++; + } + } + + return (acceptance_set_counter == number_of_acceptance_sets); +} + +} diff --git a/lbtt/src/ProductAutomaton.h b/lbtt/src/ProductAutomaton.h new file mode 100644 index 000000000..060491c7d --- /dev/null +++ b/lbtt/src/ProductAutomaton.h @@ -0,0 +1,596 @@ +/* + * 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 PRODUCTAUTOMATON_H +#define PRODUCTAUTOMATON_H + +#include +#include +#include +#include +#include "LbttAlloc.h" +#include "BitArray.h" +#include "BuchiAutomaton.h" +#include "EdgeContainer.h" +#include "Exception.h" +#include "Graph.h" +#include "StateSpace.h" + +using namespace std; + +extern bool user_break; + +namespace UserCommands +{ + extern void printAutomatonAnalysisResults + (ostream&, int, unsigned long int, unsigned long int); +} + +namespace Graph +{ + +/****************************************************************************** + * + * A class for representing the synchronous product of a Büchi automaton and + * a state space. + * + *****************************************************************************/ + +class ProductAutomaton : public Graph +{ +private: + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + class ProductState : /* A class for */ + public Graph::Node /* representing the */ + { /* states of the product + * automaton. + */ + public: + explicit ProductState /* Constructor. */ + (const size_type hash_val = 0); + + ~ProductState(); /* Destructor. */ + + /* `edges' inherited from Graph::Node */ + + size_type hashValue() const; /* Get or set the hash */ + size_type& hashValue(); /* value for the product + * state (this value can + * be used to extract + * the identifiers of + * the original state + * space and the Büchi + * automaton with which + * the product state is + * associated). + */ + + void print /* Writes information */ + (ostream& stream = cout, /* about the product */ + const int indent = 0, /* state to a stream. */ + const GraphOutputFormat fmt = NORMAL) const; + + private: + friend class ProductAutomaton; + + ProductState(const ProductState&); /* Prevent copying and */ + ProductState& operator=(const ProductState&); /* assignment of + * ProductState objects. + */ + + size_type hash_value; /* Hash value for the + * product state (can be + * used to extract the + * identifiers of the + * original state space and + * the Büchi automaton with + * which the product state + * is associated). + */ + + Edge* incoming_edge; /* The unique edge pointing + * to `this' ProductState. + */ + }; + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + class ProductScc : /* A class for storing */ + public vector /* connected components */ + { /* of the product. + */ + public: + ProductScc(); /* Constructor. */ + + /* default copy constructor */ + + ~ProductScc(); /* Destructor. */ + + /* default assignment operator */ + + bool fair /* Tests whether the */ + (const ProductAutomaton& product_automaton) /* component is fair, */ + const; /* i.e. it is a + * nontrivial component + * with a state from + * each acceptance set + * of the Büchi + * automaton used for + * constructing a + * given product. + */ + + void insert /* Inserts a state into */ + (const ProductAutomaton::size_type /* the container. */ + product_state); + }; + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +public: + class ProductSizeException; /* An exception class for + * reporting the situation + * where the size of the + * product automaton may + * be too big. + */ + + friend class ProductScc; + friend void UserCommands::printAutomatonAnalysisResults + (ostream&, int, unsigned long int, unsigned long int); + + ProductAutomaton(); /* Constructor. */ + + ~ProductAutomaton(); /* Destructor. */ + + ProductState& operator[](const size_type index) /* Indexing operator. No */ + const; /* range check is performed + * on the argument. + */ + + ProductState& node(const size_type index) const; /* Synonym for the indexing + * operator. This function + * also checks the range of + * the argument. + */ + + /* `size' inherited from Graph */ + + /* `empty' inherited from Graph */ + + void clear(); /* Makes the automaton + * empty. + */ + + void connect /* Connects two states */ + (const size_type father, /* of the product */ + const size_type child); /* automaton. */ + + void disconnect /* Disconnects two */ + (const size_type father, /* states of the product */ + const size_type child); /* automaton. */ + + /* `connected' inherited from Graph */ + + /* `stats' inherited from Graph */ + + /* `subgraphStats' inherited from Graph */ + + void computeProduct /* Function for */ + (const BuchiAutomaton& automaton, /* initializing the */ + const StateSpace& statespace, /* product automaton. */ + const bool global_product); + + StateSpace::size_type systemState /* Returns the */ + (const size_type state) const; /* identifier of the + * state of the original + * state space with + * which a given product + * state is associated. + */ + + BuchiAutomaton::size_type buchiState /* Returns the */ + (const size_type state) const; /* identifier of the + * state of the original + * automaton with which + * a given product state + * is associated. + */ + + void emptinessCheck(Bitset& result) const; /* Performs an emptiness + * check on the product. + */ + + void findAcceptingExecution /* Extracts an accepting */ + (const StateSpace::size_type initial_state, /* execution from the */ + pair, /* product automaton. */ + deque >& + execution) const; + + void print /* Writes information */ + (ostream& stream = cout, /* about the product */ + const int indent = 0, /* automaton to a */ + const GraphOutputFormat fmt = NORMAL) const; /* stream. */ + +private: + ProductAutomaton(const ProductAutomaton&); /* Prevent copying and */ + ProductAutomaton& operator= /* assignment of */ + (const ProductAutomaton&); /* ProductAutomaton + * objects. + */ + + size_type expand(size_type node_count = 1); /* Inserts states to the + * product automaton. + */ + + const BuchiAutomaton* buchi_automaton; /* A pointer to the + * Büchi automaton used for + * constructing the + * product. + */ + + StateSpace::size_type statespace_size; /* Size of the state space + * used for constructing + * the product automaton. + */ + +#ifdef HAVE_OBSTACK_H /* Storage for product */ + ObstackAllocator store; /* states and */ +#endif /* HAVE_OBSTACK_H */ /* transitions. */ +}; + + + +/****************************************************************************** + * + * An exception class for reporting the situation where the product may be too + * big to compute. + * + *****************************************************************************/ + +class ProductAutomaton::ProductSizeException : public Exception +{ +public: + ProductSizeException(); /* Constructor. */ + + /* default copy constructor */ + + ~ProductSizeException() throw(); /* Destructor. */ + + ProductSizeException& /* Assignment operator. */ + operator=(const ProductSizeException& e); + + /* `what' inherited from class Exception */ +}; + + + +/****************************************************************************** + * + * Inline function definitions for class ProductAutomaton. + * + *****************************************************************************/ + +/* ========================================================================= */ +inline ProductAutomaton::ProductAutomaton() : + buchi_automaton(0), statespace_size(0) +#ifdef HAVE_OBSTACK_H +, store() +#endif /* HAVE_OBSTACK_H */ +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class ProductAutomaton. Initializes a + * new object for storing the product of a Büchi automaton and a + * state space. The product must then be explicitly initialized + * by calling the function `computeProduct' on the object. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline ProductAutomaton::~ProductAutomaton() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class ProductAutomaton. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + clear(); +} + +/* ========================================================================= */ +inline ProductAutomaton::ProductState& +ProductAutomaton::operator[](const size_type index) const +/* ---------------------------------------------------------------------------- + * + * Description: Indexing operator for class ProductAutomaton. Can be used to + * refer to the individual states of the product automaton. No + * range check will be performed on the argument. + * + * Argument: index -- Index of a state of the product automaton. + * + * Returns: A reference to the product state corresponding to the index. + * + * ------------------------------------------------------------------------- */ +{ + return static_cast(*nodes[index]); +} + +/* ========================================================================= */ +inline ProductAutomaton::ProductState& +ProductAutomaton::node(const size_type index) const +/* ---------------------------------------------------------------------------- + * + * Description: Function for referring to a single state of a + * ProductAutomaton. This function will also check the range + * argument. + * + * Argument: index -- Index of a state of the product automaton. + * + * Returns: A reference to the corresponding product state. + * + * ------------------------------------------------------------------------- */ +{ + return static_cast(Graph::node(index)); +} + +/* ========================================================================= */ +inline StateSpace::size_type ProductAutomaton::systemState + (const size_type state) const +/* ---------------------------------------------------------------------------- + * + * Description: Returns the identifier of the system state with which a + * given product state is associated. This function will perform + * no range checks on its argument. + * + * Argument: state -- Identifier of a product state. + * + * Returns: Identifier of a state in a state space. + * + * ------------------------------------------------------------------------- */ +{ + return operator[](state).hashValue() % statespace_size; +} + +/* ========================================================================= */ +inline StateSpace::size_type ProductAutomaton::buchiState + (const size_type state) const +/* ---------------------------------------------------------------------------- + * + * Description: Returns the identifier of the state of the Büchi automaton + * with which a given product state is associated. This function + * will perform no range checks on its argument. + * + * Argument: state -- Identifier of a product state. + * + * Returns: Identifier of a state in a Büchi automaton. + * + * ------------------------------------------------------------------------- */ +{ + return operator[](state).hashValue() / statespace_size; +} + + + +/****************************************************************************** + * + * Inline function definitions for class ProductAutomaton::ProductState. + * + *****************************************************************************/ + +/* ========================================================================= */ +inline ProductAutomaton::ProductState::ProductState(const size_type hash_val) : + Graph::Node(), hash_value(hash_val), incoming_edge(0) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class ProductAutomaton::ProductState. Creates + * a new object representing a synchronous product of a state of + * a Büchi automaton with a state of a state space. + * + * Arguments: hash_val -- Hash value for the product state. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline ProductAutomaton::ProductState::~ProductState() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class ProductAutomaton::ProductState. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + if (incoming_edge != 0) + { +#ifdef HAVE_OBSTACK_H + incoming_edge->~Edge(); +#else + delete incoming_edge; +#endif /* HAVE_OBSTACK_H */ + } + outgoing_edges.clear(); +} + +/* ========================================================================= */ +inline ProductAutomaton::size_type ProductAutomaton::ProductState::hashValue() + const +/* ---------------------------------------------------------------------------- + * + * Description: Returns the product state's hash value by value. + * + * Arguments: None. + * + * Returns: The hash value of the product state. + * + * ------------------------------------------------------------------------- */ +{ + return hash_value; +} + +/* ========================================================================= */ +inline ProductAutomaton::size_type& ProductAutomaton::ProductState::hashValue() +/* ---------------------------------------------------------------------------- + * + * Description: Returns the product state's hash value by reference. (This + * function can therefore be used to change the value.) + * + * Arguments: None. + * + * Returns: A reference to the hash value of the product state. + * + * ------------------------------------------------------------------------- */ +{ + return hash_value; +} + + + +/****************************************************************************** + * + * Inline function definitions for class ProductAutomaton::ProductScc. + * + *****************************************************************************/ + +/* ========================================================================= */ +inline ProductAutomaton::ProductScc::ProductScc() +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class ProductAutomaton::ProductScc. Creates a + * new container for storing a maximal strongly connected + * component of a ProductAutomaton. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline ProductAutomaton::ProductScc::~ProductScc() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class ProductAutomaton::ProductScc. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline void ProductAutomaton::ProductScc::insert + (const ProductAutomaton::size_type product_state) +/* ---------------------------------------------------------------------------- + * + * Description: Inserts a new product state identifier to the container. + * + * Argument: product_state -- Identifier of a product state. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + push_back(product_state); +} + + + +/****************************************************************************** + * + * Inline function definitions for class + * ProductAutomaton::ProductSizeException. + * + *****************************************************************************/ + +/* ========================================================================= */ +inline ProductAutomaton::ProductSizeException::ProductSizeException() : + Exception("product may be too large") +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class ProductAutomaton::ProductSizeException. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline ProductAutomaton::ProductSizeException::~ProductSizeException() throw() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class ProductAutomaton::ProductSizeException. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline ProductAutomaton::ProductSizeException& +ProductAutomaton::ProductSizeException::operator= + (const ProductAutomaton::ProductSizeException& e) +/* ---------------------------------------------------------------------------- + * + * Description: Assignment operator for class + * ProductAutomaton::ProductSizeException. Assigns the value of + * another ProductAutomaton::ProductSizeException to `this' one. + * + * Arguments: e -- A reference to a constant + * ProductAutomaton::ProductSizeException. + * + * Returns: A reference to the object whose value was changed. + * + * ------------------------------------------------------------------------- */ +{ + Exception::operator=(e); + return *this; +} + +} + +#endif /* !PRODUCTAUTOMATON_H */ diff --git a/lbtt/src/Random.h b/lbtt/src/Random.h index 2650599a5..bd69ec27c 100644 --- a/lbtt/src/Random.h +++ b/lbtt/src/Random.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lbtt/src/SccCollection.h b/lbtt/src/SccCollection.h index ea31ac97f..32c5fc294 100644 --- a/lbtt/src/SccCollection.h +++ b/lbtt/src/SccCollection.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -195,10 +195,8 @@ public: /* default assignment operator */ - typedef set, /* a set of node id's. */ - ALLOC(typename GraphType::size_type) > - SccType; + typedef set /* Type definition for */ + SccType; /* a set of node id's. */ const SccType& operator()() const; /* Returns the set of node * identifiers in a @@ -338,10 +336,10 @@ public: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ typedef map, /* identifiers and the */ - ALLOC(typename GraphType::size_type) >/* order in which they */ - DfsOrdering; /* were encountered in + typename GraphType::size_type> /* mapping between node */ + DfsOrdering; /* identifiers and the + * order in which they + * were encountered in * the search for * strongly connected * components. @@ -433,18 +431,19 @@ public: typename GraphType::size_type lowlink; }; - deque /* backtracking stack. */ - node_stack; + deque node_stack; /* Depth-first search + * backtracking stack. + */ + NodeStackElement* current_node; /* Pointer to the top * element of the * backtracking stack. */ - deque /* collecting the nodes */ - scc_stack; /* in a strongly + deque scc_stack; /* Stack used for + * collecting the nodes + * in a strongly * connected component, * excluding the root * nodes of the @@ -998,8 +997,7 @@ void SccCollection::iterator::getPath * when exiting from this function). */ - typename deque::const_iterator - n = node_stack.begin(); + typename deque::const_iterator n = node_stack.begin(); if (n != node_stack.end()) { for (++n; n != node_stack.end(); ++n) diff --git a/lbtt/src/SccIterator.h b/lbtt/src/SccIterator.h new file mode 100644 index 000000000..fceaceeea --- /dev/null +++ b/lbtt/src/SccIterator.h @@ -0,0 +1,752 @@ +/* + * 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 SCCITERATOR_H +#define SCCITERATOR_H + +#include +#include +#include +#include +#include +#include "LbttAlloc.h" +#include "Graph.h" + +using namespace std; + +namespace Graph +{ + +/****************************************************************************** + * + * A template iterator class for computing the maximal strongly connected + * components of a graph represented as an object of class + * Graph. + * + * The iterator class has three template arguments: + * class EdgeContainer -- Container class storing the edges in the + * graph with which the iterator is associated. + * + * class SccContainer -- Container for storing the identifiers of the + * nodes belonging to some maximal strongly + * connected component. The container class + * must be able to store elements of type + * Graph::size_type. The container + * class interface must support the following + * operations: + * + * Default constructor which can be called + * without any arguments + * Copy constructor + * Assignment operator + * clear() + * [makes the container empty] + * insert(Graph::size_type s) + * [inserts an element into the + * container] + * + * If the container class is left unspecified, + * it defaults to + * set::size_type, + * less::size_type>, + * ALLOC(Graph::size_type)>. + * + * class Filter -- Class for representing function objects that + * can be used to restrict the iterator + * dereferencing operators to return only + * those nodes of a strongly connected component + * which satisfy a certain condition that can be + * tested using Filter::operator(). This function + * has to accept a single parameter of type + * Graph::Node*. It must return a + * Boolean value. The nodes for which the + * function returns `false' will then not be + * included in the collection of nodes returned + * by the iterator dereferencing operators. + * + * If the Filter class is left unspecified, it + * defaults to the NullSccFilter + * class, which does not restrict the set of + * nodes in any way. + * + *****************************************************************************/ + +template +class NullSccFilter; + +template::size_type, + less::size_type>, + ALLOC(typename Graph::size_type) >, + class Filter = NullSccFilter > +class SccIterator +{ +public: + SccIterator(const Graph& g); /* Constructor. */ + + /* default copy constructor */ + + ~SccIterator(); /* Destructor. */ + + /* default assignment operator */ + + bool operator== /* Equality test for */ + (const SccIterator& it) const; + + bool operator!= /* Inequality test for */ + (const SccIterator& it) const; + + bool operator< /* `Less than' relation */ + (const SccIterator& it) const; + + bool operator<= /* `Less than or equal' */ + (const SccIterator& it) const; + + bool operator> /* `Greater than' */ + (const SccIterator& it) const; + + bool operator>= /* `Greater than or */ + (const SccIterator& it) const; /* iterators. */ + + const SccContainer& operator*() const; /* Dereferencing */ + const SccContainer* operator->() const; /* operators. */ + + const SccContainer& operator++(); /* Prefix and postfix */ + const SccContainer operator++(int); /* increment operators. */ + + bool atEnd() const; /* Tests whether the + * iterator has scanned + * through all the + * strongly connected + * components of the + * graph. + */ + +private: + const Graph& graph; /* Reference to the graph + * with which the iterator + * is associated. + */ + + typename Graph::size_type /* Number of graph */ + dfs_number; /* nodes processed by + * the iterator. + */ + + vector::size_type, /* dfs_ordering[i] */ + ALLOC(typename Graph /* indicates the */ + ::size_type) > /* position of graph */ + dfs_ordering; /* node i in the depth- + * first search order. + * (If the node has not + * yet been visited, + * dfs_ordering[i]==0.) + */ + + vector::size_type, /* lowlink[i] indicates */ + ALLOC(typename Graph /* the least graph node */ + ::size_type) > /* (in the depth-first */ + lowlink; /* search order) that + * is reachable from + * graph node i and + * does not belong to + * any strongly + * connected component + * which has already been + * processed. + */ + + typedef pair::size_type, + typename EdgeContainer::const_iterator> + NodeStackElement; + + stack > + node_stack; + + typename Graph::size_type /* Current graph node */ + current_node; /* the depth-first + * search. + */ + + typename EdgeContainer::const_iterator edge; /* Iterator to scan + * through the successors + * of the current node. + */ + + stack::size_type, /* Stack used for */ + deque /* collecting the nodes */ + ::size_type, /* in a strongly */ + ALLOC(typename Graph /* connected component. */ + ::size_type) + > + > + scc_stack; + + SccContainer current_scc; /* Container of nodes + * forming the maximal + * strongly connected + * graph component + * currently `pointed to' + * by the iterator. + */ + + Filter cond; /* Function object for + * filtering out a subset + * of nodes in the + * strongly connected + * components. + */ + + void reset(); /* Initializes the + * iterator to point to + * the first strongly + * connected component of + * the graph. + */ + + void computeNextScc(); /* Updates the iterator to + * point to the next + * strongly connected + * component. + */ +}; + + + +/****************************************************************************** + * + * Default test for collecting the nodes in a strongly connected component. + * (See documentation on class SccIterator for information about the purpose + * of the class.) + * + *****************************************************************************/ + +template +class NullSccFilter +{ +public: + bool operator()(const typename Graph::Node*) const; +}; + + + +/****************************************************************************** + * + * Inline function definitions for template class + * SccIterator. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline SccIterator::SccIterator + (const Graph& g) : + graph(g), dfs_ordering(graph.size()), lowlink(graph.size()) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class + * SccIterator. + * 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 + * (a Graph object). + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + reset(); + computeNextScc(); +} + +/* ========================================================================= */ +template +inline SccIterator::~SccIterator() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class + * SccIterator. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline bool SccIterator::operator== + (const SccIterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: Equality test for two SccIterators. Two SccIterators are + * `equal' if and only if both of them are associated with + * exactly the same graph object in memory and the iterators + * have processed the same amount of graph nodes. + * + * Arguments: it -- A constant reference to another SccIterator. + * + * Returns: A truth value according to the result of the equality test. + * + * ------------------------------------------------------------------------- */ +{ + return (&graph == &(it.graph) && dfs_number == it.dfs_number); +} + +/* ========================================================================= */ +template +inline bool SccIterator::operator!= + (const SccIterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: Inequality test for two SccIterators. Two SccIterators are + * not equal if and only if they are associated with different + * graphs or they are associated with the same graph object in + * memory but the iterators have processed a different number of + * graph nodes. + * + * Arguments: it -- A constant reference to another SccIterator. + * + * Returns: A truth value according to the result of the inequality test. + * + * ------------------------------------------------------------------------- */ +{ + return (&graph != &(it.graph) || dfs_number != it.dfs_number); +} + +/* ========================================================================= */ +template +inline bool SccIterator::operator< + (const SccIterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: `Less than' relation between two SccIterators. An + * SccIterator is `less than' another if and only if the + * iterators relate to the same graph object in memory and + * the first iterator has processed a smaller number of nodes + * than the second one. + * + * Arguments: it -- A constant reference to another SccIterator. + * + * Returns: A truth value according to the result of the test. + * + * ------------------------------------------------------------------------- */ +{ + return (&graph == &(it.graph) && dfs_number < it.dfs_number); +} + +/* ========================================================================= */ +template +inline bool SccIterator::operator<= + (const SccIterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: `Less than or equal' relation between two SccIterators. An + * SccIterator is `less than or equal to' another if and only + * if the iterators relate to the same graph object in memory + * and the first iterator has processed a number of nodes not + * exceeding the number of nodes the second iterator has + * processed. + * + * Arguments: it -- A constant reference to another SccIterator. + * + * Returns: A truth value according to the result of the test. + * + * ------------------------------------------------------------------------- */ +{ + return (&graph == &(it.graph) && dfs_number <= it.dfs_number); +} + +/* ========================================================================= */ +template +inline bool SccIterator::operator> + (const SccIterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: `Greater than' relation between two SccIterators. An + * SccIterator is `greater than' another if and only if the + * iterators relate to the same graph object in memory and + * the first iterator has processed a greater number of nodes + * than the second one. + * + * Arguments: it -- A constant reference to another SccIterator. + * + * Returns: A truth value according to the result of the test. + * + * ------------------------------------------------------------------------- */ +{ + return (&graph == &(it.graph) && dfs_number > it.dfs_number); +} + +/* ========================================================================= */ +template +inline bool SccIterator::operator>= + (const SccIterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: `Greater than or equal' relation between two SccIterators. An + * SccIterator is `greater than or equal to' another if and + * only if the iterators relate to the same graph object in + * memory and the first iterator has processed at least as many + * nodes as the second iterator has processed. + * + * Arguments: it -- A constant reference to another SccIterator. + * + * Returns: A truth value according to the result of the test. + * + * ------------------------------------------------------------------------- */ +{ + return (&graph == &(it.graph) && dfs_number >= it.dfs_number); +} + +/* ========================================================================= */ +template +inline const SccContainer& +SccIterator::operator*() const +/* ---------------------------------------------------------------------------- + * + * Description: Dereferencing operator for a SccIterator. Returns the + * collection of nodes which belong to the maximal strongly + * connected component that the iterator currently points to. + * + * Arguments: None. + * + * Returns: A collection of nodes representing some maximal strongly + * connected component. + * + * ------------------------------------------------------------------------- */ +{ + return current_scc; +} + +/* ========================================================================= */ +template +inline const SccContainer* +SccIterator::operator->() const +/* ---------------------------------------------------------------------------- + * + * Description: Dereferencing operator for a SccIterator. Returns the + * collection of nodes which belong to the maximal strongly + * connected component that the iterator currently points to. + * + * Arguments: None. + * + * Returns: A collection of nodes representing some maximal strongly + * connected component. + * + * ------------------------------------------------------------------------- */ +{ + return ¤t_scc; +} + +/* ========================================================================= */ +template +inline const SccContainer& +SccIterator::operator++() +/* ---------------------------------------------------------------------------- + * + * Description: Prefix increment operator for a SccIterator. Computes the + * next maximal strongly connected component of the graph and + * then returns it. + * + * Arguments: None. + * + * Returns: A collection of nodes representing some maximal strongly + * connected component. + * + * ------------------------------------------------------------------------- */ +{ + computeNextScc(); + return current_scc; +} + +/* ========================================================================= */ +template +inline const SccContainer +SccIterator::operator++(int) +/* ---------------------------------------------------------------------------- + * + * Description: Postfix increment operator for a SccIterator. Effectively + * returns the maximal strongly connected component of the graph + * currently pointed to by the iterator and then updates the + * iterator to point to the next strongly connected component. + * + * Arguments: None (the `int' is only required to distinguish this operator + * from the prefix increment operator). + * + * Returns: A collection of nodes representing some maximal strongly + * connected component. + * + * ------------------------------------------------------------------------- */ +{ + SccContainer old_scc = current_scc; + computeNextScc(); + return old_scc; +} + +/* ========================================================================= */ +template +inline bool SccIterator::atEnd() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells whether there are still more strongly connected + * components in the graph for the iterator to process. + * + * Arguments: None. + * + * Returns: A truth value. + * + * ------------------------------------------------------------------------- */ +{ + return (current_node == graph.size()); +} + + + +/****************************************************************************** + * + * Function definitions for template class + * SccIterator. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +void SccIterator::reset() +/* ---------------------------------------------------------------------------- + * + * Description: Initializes the iterator to point to the first maximal + * strongly connected component of the graph with which the + * iterator it associated. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + dfs_number = 0; + + for (typename vector::size_type, + ALLOC(typename Graph::size_type) > + ::iterator node = dfs_ordering.begin(); + node != dfs_ordering.end(); + ++node) + *node = 0; + + while (!node_stack.empty()) + node_stack.pop(); + + while (!scc_stack.empty()) + scc_stack.pop(); + + current_scc.clear(); +} + +/* ========================================================================= */ +template +void SccIterator::computeNextScc() +/* ---------------------------------------------------------------------------- + * + * Description: Updates the state of the iterator to `point to' the next + * maximal strongly connected component of the graph, using the + * algorithm due to Tarjan [R. Tarjan. Depth-first search and + * linear graph algorithms. SIAM Journal on Computing, + * 1(2):146--160, June 1972] for computing the next maximal + * strongly connected component of the graph. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + current_scc.clear(); + + if (scc_stack.empty() && node_stack.empty()) + { + /* + * If both `scc_stack' and `node_stack' are empty (this holds if we have + * recently finished processing some component of the graph), try to find + * a graph node that has not yet been visited. If no such node is found, + * all nodes have been visited and there are no more strongly connected + * components to be found in the graph. + */ + + current_node = 0; + for (typename vector::size_type, + ALLOC(typename Graph::size_type) > + ::const_iterator node = dfs_ordering.begin(); + node != dfs_ordering.end() && (*node) != 0; + ++node) + ++current_node; + + if (current_node == graph.size()) + return; + + /* + * Prepare to continue the depth-first search in the unvisited node. + */ + + edge = graph[current_node].edges().begin(); + + scc_stack.push(current_node); + ++dfs_number; + dfs_ordering[current_node] = lowlink[current_node] = dfs_number; + } + + typename Graph::size_type child_node; + + while (1) + { + /* + * If there are still nodes left in the depth-first search backtracking + * stack, pop a node and its next unprocessed outgoing edge off the stack. + * Before continuing the depth-first search in the popped node, update + * its lowlink value if necessary. (This has to be done if the lowlink of + * the current node---a successor of the popped node---is less than the + * lowlink of the popped node but not equal to zero.) + */ + + if (!node_stack.empty()) + { + typename Graph::size_type father_node + = node_stack.top().first; + edge = node_stack.top().second; + node_stack.pop(); + + if (lowlink[current_node] < lowlink[father_node] + && lowlink[current_node] != 0) + lowlink[father_node] = lowlink[current_node]; + + current_node = father_node; + } + + /* + * Scan through the successors of the current node. + * + * If the current nodes has an unvisited successor node (a successor i + * with dfs_ordering[i] == 0), push the current node and its next + * unprocessed edge onto the backtracking stack and then continue the + * search in the successor node. Push also the successor node onto the + * strongly connected component stack. + * + * Otherwise, update the lowlink of the current node to the lowlink of + * its already visited successor if necessary. + */ + + while (edge != graph[current_node].edges().end()) + { + child_node = (*edge)->targetNode(); + ++edge; + + if (dfs_ordering[child_node] == 0) + { + node_stack.push(make_pair(current_node, edge)); + scc_stack.push(child_node); + + ++dfs_number; + dfs_ordering[child_node] = lowlink[child_node] = dfs_number; + + current_node = child_node; + edge = graph[current_node].edges().begin(); + } + else if (lowlink[child_node] < lowlink[current_node] + && lowlink[child_node] != 0) + lowlink[current_node] = lowlink[child_node]; + } + + /* + * If the least node in the depth-first search order reachable from the + * current node is the current node itself at the end of the previous + * loop, we have found a maximal strongly connected component of the + * graph. In this case, collect the states satisfying `cond' in the + * strongly connected component stack to form the component and exit. + * (Otherwise, return to the start of the outermost while loop and + * continue by popping a state off the depth-first search backtracking + * stack.) + */ + + if (dfs_ordering[current_node] == lowlink[current_node]) + { + do + { + child_node = scc_stack.top(); + scc_stack.pop(); + if (cond(&graph[child_node])) + current_scc.insert(child_node); + lowlink[child_node] = 0; + } + while (child_node != current_node); + + break; + } + } +} + + + +/****************************************************************************** + * + * Inline function definitions for template class NullSccFilter. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline bool NullSccFilter::operator() + (const typename Graph::Node*) const +/* ---------------------------------------------------------------------------- + * + * Description: Default test for filtering the nodes in a strongly connected + * graph component. The default is to simply include all nodes + * in the result. + * + * Arguments: A constant pointer to a Graph::Node (required + * only to satisfy the class interface requirements). + * + * Returns: true, so the test will succeed for every node in the + * component. + * + * ------------------------------------------------------------------------- */ +{ + return true; +} + +} + +#endif /* !SCCITERATOR_H */ diff --git a/lbtt/src/SharedTestData.h b/lbtt/src/SharedTestData.h index 6403c52d4..6820d310d 100644 --- a/lbtt/src/SharedTestData.h +++ b/lbtt/src/SharedTestData.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -42,14 +42,14 @@ extern TestRoundInfo round_info; /* Data structure for * round. */ -extern vector /* implementation. */ - test_results; - -extern vector /* statistics for each */ - final_statistics; /* implementation. */ +extern vector test_results; /* Test results for each + * implementation. + */ +extern vector final_statistics; /* Overall test + * statistics for each + * implementation. + */ } #endif /* !SHAREDTESTDATA_H */ diff --git a/lbtt/src/SpinWrapper.cc b/lbtt/src/SpinWrapper.cc index 1b21aa68c..f98d59792 100644 --- a/lbtt/src/SpinWrapper.cc +++ b/lbtt/src/SpinWrapper.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,10 +17,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #ifdef HAVE_SSTREAM #include diff --git a/lbtt/src/SpinWrapper.h b/lbtt/src/SpinWrapper.h index 233d62b06..1e4a7ed52 100644 --- a/lbtt/src/SpinWrapper.h +++ b/lbtt/src/SpinWrapper.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,10 +20,6 @@ #ifndef SPINWRAPPER_H #define SPINWRAPPER_H -#ifdef __GNUC__ -#pragma interface -#endif /* __GNUC__ */ - #include #include #include "ExternalTranslator.h" diff --git a/lbtt/src/StatDisplay.cc b/lbtt/src/StatDisplay.cc index 6da2df696..8f8b772d0 100644 --- a/lbtt/src/StatDisplay.cc +++ b/lbtt/src/StatDisplay.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,14 +17,11 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #include #include "DispUtil.h" #include "Exception.h" +#include "IntervalList.h" #include "SharedTestData.h" #include "StatDisplay.h" #include "StringUtil.h" @@ -37,11 +34,51 @@ 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, - vector::size_type - algorithm, + vector::size_type algorithm, int result_id) /* ---------------------------------------------------------------------------- * @@ -52,7 +89,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 @@ -67,43 +104,75 @@ 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(); } /* ========================================================================= */ void printProductAutomatonStats (ostream& stream, int indent, - vector::size_type - algorithm, + vector::size_type algorithm, int result_id) /* ---------------------------------------------------------------------------- * @@ -114,7 +183,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 @@ -129,51 +198,71 @@ 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(); } /* ========================================================================= */ void printAcceptanceCycleStats (ostream& stream, int indent, - vector::size_type - algorithm, + vector::size_type algorithm, int result_id) /* ---------------------------------------------------------------------------- * @@ -185,7 +274,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. @@ -201,49 +290,64 @@ 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(); } /* ========================================================================= */ void printConsistencyCheckStats (ostream& stream, int indent, - vector::size_type - algorithm) + vector::size_type algorithm) /* ---------------------------------------------------------------------------- * * Description: Displays information about the consistency check result for @@ -253,7 +357,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. * @@ -264,100 +368,113 @@ 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(); + for (vector::size_type 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) @@ -385,89 +502,75 @@ 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; + for (vector::size_type 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] @@ -482,7 +585,7 @@ void printBuchiIntersectionCheckStats estream << string(counter == -1 ? "N/A " : "failed") + ' '; - if (alg_1 != alg_2) + if (*alg_1 != alg_2) { estream << '('; if (pos_test) @@ -493,9 +596,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) @@ -511,18 +614,18 @@ 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(); } /* ========================================================================= */ void printAllStats (ostream& stream, int indent, - vector::size_type - algorithm) + vector::size_type algorithm) /* ---------------------------------------------------------------------------- * * Description: Displays all test information (Büchi automaton and product @@ -541,30 +644,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); } @@ -575,8 +694,8 @@ void printAllStats /* ========================================================================= */ void printCollectiveCrossComparisonStats (ostream& stream, - vector::size_type algorithm_y, - vector::size_type algorithm_x, + vector::size_type algorithm_y, + vector::size_type algorithm_x, int data_type) /* ---------------------------------------------------------------------------- * @@ -630,10 +749,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; @@ -768,13 +885,12 @@ void printCollectiveStats(ostream& stream, int indent) if (round_info.num_processed_formulae > 0 && configuration.global_options.formula_input_filename.empty()) { - const map, - ALLOC(unsigned long int) >& + const map& proposition_statistics = configuration.formula_options.formula_generator. propositionStatistics(); - const map, ALLOC(unsigned long int) > + const map symbol_statistics = configuration.formula_options.formula_generator.symbolStatistics(); @@ -820,9 +936,8 @@ void printCollectiveStats(ostream& stream, int indent) number_of_symbols_printed++; } - for (map, ALLOC(unsigned long int) > - ::const_iterator proposition = proposition_statistics.begin(); + for (map::const_iterator + proposition = proposition_statistics.begin(); proposition != proposition_statistics.end(); ++proposition) { @@ -869,8 +984,8 @@ void printCollectiveStats(ostream& stream, int indent) = ""; number_of_symbols_printed = 0; - for (map, ALLOC(unsigned long int) > - ::const_iterator op = symbol_statistics.begin(); + for (map::const_iterator + op = symbol_statistics.begin(); op != symbol_statistics.end(); ++op) { @@ -912,6 +1027,8 @@ void printCollectiveStats(ostream& stream, int indent) if (number_of_symbols_printed % 5 != 0) { + if (number_of_symbols_printed > 5) + estream << '\n'; estream << ind + " operator " + symbol_name_string + '\n' + ind + " # " + symbol_number_string + '\n' + ind + " #/formula " + symbol_distribution_string @@ -953,8 +1070,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) { @@ -967,14 +1087,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++) { @@ -1119,7 +1239,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, ' ') @@ -1339,8 +1459,7 @@ void printCollectiveStats(ostream& stream, int indent) estream << ind + " Result inconsistency statistics\n" + ind + " " + string(31, '=') + '\n'; - vector::size_type - algorithm_x, algorithm_y; + vector::size_type algorithm_x, algorithm_y; for (algorithm_x = 0; algorithm_x < number_of_algorithms; algorithm_x += 2) @@ -1352,7 +1471,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(), ' '); } @@ -1362,6 +1481,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) @@ -1372,7 +1494,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..a5cb99b45 100644 --- a/lbtt/src/StatDisplay.h +++ b/lbtt/src/StatDisplay.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -42,65 +42,60 @@ 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. */ - vector::size_type + vector::size_type algorithm, int result_id); void printProductAutomatonStats /* Displays information */ (ostream& stream, /* about a product */ int indent, /* automaton. */ - vector::size_type + vector::size_type algorithm, int result_id); void printAcceptanceCycleStats /* Displays information */ (ostream& stream, /* about the acceptance */ int indent, /* cycles of a product */ - vector::size_type + vector::size_type /* automaton. */ algorithm, int result_id); void printConsistencyCheckStats /* Displays the result */ (ostream& stream, /* of the consistency */ int indent, /* check for a given */ - vector::size_type + vector::size_type /* algorithm. */ 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 */ int indent, /* information displayed */ - vector::size_type /* functions. */ - algorithm); + vector::size_type algorithm); /* by the previous five + * functions. + */ void printCollectiveCrossComparisonStats /* Displays a single */ (ostream& stream, /* `cell' of the final */ - vector::size_type /* comparison table. */ - algorithm_y, - vector::size_type - algorithm_x, + vector::size_type algorithm_y, /* result cross- */ + vector::size_type algorithm_x, /* comparison table. */ int data_type); void printCollectiveStats /* Displays average test */ diff --git a/lbtt/src/StateSpace.cc b/lbtt/src/StateSpace.cc index 90a155f3a..3c16064ea 100644 --- a/lbtt/src/StateSpace.cc +++ b/lbtt/src/StateSpace.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,10 +17,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #include #include "DispUtil.h" @@ -148,8 +144,7 @@ void StateSpace::clear() initial_state = 0; #ifdef HAVE_OBSTACK_H - for (vector::iterator state = nodes.begin(); - state != nodes.end(); + for (vector::iterator state = nodes.begin(); state != nodes.end(); ++state) static_cast(*state)->~State(); diff --git a/lbtt/src/StateSpace.h b/lbtt/src/StateSpace.h index 338a5a4d1..cb67a6782 100644 --- a/lbtt/src/StateSpace.h +++ b/lbtt/src/StateSpace.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lbtt/src/StateSpaceProduct.h b/lbtt/src/StateSpaceProduct.h index 87fab2fbd..25a5c8a4b 100644 --- a/lbtt/src/StateSpaceProduct.h +++ b/lbtt/src/StateSpaceProduct.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lbtt/src/StateSpaceRandomizer.cc b/lbtt/src/StateSpaceRandomizer.cc index 951b4567c..f836d72a1 100644 --- a/lbtt/src/StateSpaceRandomizer.cc +++ b/lbtt/src/StateSpaceRandomizer.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -174,9 +174,7 @@ StateSpace* StateSpaceRandomizer::generateConnectedGraph() const StateSpace::size_type first_unreachable_state = 1, random_node; - multimap, - ALLOC(StateSpace::size_type) > - reachable_but_unprocessed; + multimap reachable_but_unprocessed; reachable_but_unprocessed.insert(make_pair(0, 0)); diff --git a/lbtt/src/StateSpaceRandomizer.h b/lbtt/src/StateSpaceRandomizer.h index 03dd3ee07..f74474490 100644 --- a/lbtt/src/StateSpaceRandomizer.h +++ b/lbtt/src/StateSpaceRandomizer.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,10 +20,6 @@ #ifndef STATESPACERANDOMIZER_H #define STATESPACERANDOMIZER_H -#ifdef __GNUC__ -#pragma interface -#endif /* __GNUC__ */ - #include #include "Random.h" #include "StateSpace.h" diff --git a/lbtt/src/StringUtil.cc b/lbtt/src/StringUtil.cc index e60b354fe..45d31b2ec 100644 --- a/lbtt/src/StringUtil.cc +++ b/lbtt/src/StringUtil.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,11 +17,9 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include +#include +#include #include #include "StringUtil.h" @@ -63,8 +61,7 @@ string toString(const double d, const int precision, const ios::fmtflags flags) /* ========================================================================= */ void sliceString - (const string& s, const char* slice_chars, - vector& slices) + (const string& s, const char* slice_chars, vector& slices) /* ---------------------------------------------------------------------------- * * Description: Slices a string into a vector of strings, using a given set @@ -104,6 +101,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) /* ---------------------------------------------------------------------------- @@ -119,9 +321,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 + "'"); @@ -129,98 +333,253 @@ 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; - - number_set.clear(); - - for (vector::const_iterator - interval = intervals.begin(); - interval != intervals.end(); - ++interval) + if (token != "*") { - if (*interval == "*") + string::size_type pos(token.find_first_of("-")); + if (pos == string::npos) + pos = token.find("..."); + string value(token.substr(0, pos)); + + if (!value.empty()) { - for (unsigned long int i = min; i <= max; i++) - number_set.insert(i); - break; + tmp_min = parseNumber(value); + if (pos == string::npos) + tmp_max = tmp_min; + interval_type |= LEFT_BOUNDED; } - dash_pos = (*interval).find_first_of("-"); + if (pos != string::npos) + value = token.substr(pos + (token[pos] == '-' ? 1 : 3)); - if (dash_pos == (*interval).npos) - number_set.insert(parseNumber(*interval)); - else + if (!value.empty()) { - if (dash_pos == 0) + 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 i = interval_strings.begin(); + i != interval_strings.end(); + ++i) + { + unsigned long int i_start, i_end; + + try + { + interval_type = parseInterval(*i, i_start, i_end); + } + catch (const NotANumberException&) + { + 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..954f936d6 100644 --- a/lbtt/src/StringUtil.h +++ b/lbtt/src/StringUtil.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,6 +31,7 @@ #include #include "LbttAlloc.h" #include "Exception.h" +#include "IntervalList.h" using namespace std; @@ -57,22 +58,71 @@ template string toString(const T& t); /* Template function for void sliceString /* Breaks a string into */ (const string& s, const char* slice_chars, /* `slices', using a */ - vector& slices); /* given set of + vector& slices); /* given set of * characters as * 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 = 0); /* union of the + * 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 +183,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 +271,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 index 51dc9c335..45f9ad676 100644 --- a/lbtt/src/TempFsysName.cc +++ b/lbtt/src/TempFsysName.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 2004 - * Heikki Tauriainen + * Copyright (C) 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lbtt/src/TempFsysName.h b/lbtt/src/TempFsysName.h index f2845ebb6..d9fa07994 100644 --- a/lbtt/src/TempFsysName.h +++ b/lbtt/src/TempFsysName.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 2004 - * Heikki Tauriainen + * Copyright (C) 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lbtt/src/TestOperations.cc b/lbtt/src/TestOperations.cc index 4504fd4fb..01e41b2a2 100644 --- a/lbtt/src/TestOperations.cc +++ b/lbtt/src/TestOperations.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,30 +18,52 @@ */ #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" + + + +extern pid_t translator_process; /****************************************************************************** * @@ -57,6 +79,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) @@ -131,13 +166,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. * @@ -145,10 +186,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); } @@ -205,7 +288,7 @@ void printFileContents first_line_printed = true; estream << string(indent, ' ') + message + '\n'; } - + estream << string(indent, ' ') + line_prefix + message_line + '\n'; } } @@ -243,8 +326,8 @@ void writeToTranscript(const string& message, bool show_formula_in_header) const string roundstring = "Round " + toString(round_info.current_round); round_info.transcript_file << roundstring + '\n' - + string(roundstring.length(), '-') - + "\n\n"; + + string(roundstring.length(), '-') + + "\n\n"; if (show_formula_in_header) { @@ -289,7 +372,7 @@ void generateStateSpace() using ::Graph::StateSpace; if (configuration.global_options.statespace_generation_mode - == Configuration::ENUMERATEDPATH) + == Configuration::ENUMERATEDPATH) { StateSpace::size_type current_size = configuration.statespace_generator.min_size; @@ -317,7 +400,7 @@ void generateStateSpace() 2, 6); } - else + else { current_size = configuration.statespace_generator.min_size; printText("[All state spaces have been enumerated. Staring over]\n", @@ -331,7 +414,7 @@ void generateStateSpace() { round_info.path_iterator = new Graph::PathIterator - (configuration.statespace_generator.atoms_per_state, + (configuration.statespace_generator.atoms_per_state, current_size); } @@ -365,16 +448,16 @@ void generateStateSpace() { switch (configuration.global_options.statespace_generation_mode) { - case Configuration::RANDOMGRAPH : + case Configuration::RANDOMGRAPH : statespace = configuration.statespace_generator.generateGraph(); break; - case Configuration::RANDOMCONNECTEDGRAPH : + case Configuration::RANDOMCONNECTEDGRAPH : statespace = configuration.statespace_generator. - generateConnectedGraph(); + generateConnectedGraph(); break; - default : /* Configuration::RANDOMPATH */ + default : /* Configuration::RANDOMPATH */ statespace = configuration.statespace_generator.generatePath(); break; } @@ -405,8 +488,8 @@ void generateStateSpace() printText(" ok\n", 4); - if (configuration.statespace_generator.max_size - > configuration.statespace_generator.min_size) + if (configuration.statespace_generator.max_size + > configuration.statespace_generator.min_size) printText("number of states: " + toString(round_info.statespace->size()) + '\n', @@ -514,7 +597,7 @@ void generateFormulae(istream* formula_input_stream) { bool fatal_io_error = (configuration.global_options.formula_input_filename.empty() - || !round_info.formula_input_file.eof()); + || !round_info.formula_input_stream->eof()); printText(string("[") + (fatal_io_error ? "Error reading formula" @@ -570,7 +653,7 @@ void generateFormulae(istream* formula_input_stream) if (printText(" ok\n", 4)) printText("", 4, 6); - + round_info.formulae[3] = &(::Ltl::Not::construct(*round_info.formulae[2])); if (printText(" ok\n", 4)) @@ -585,7 +668,7 @@ void generateFormulae(istream* formula_input_stream) for (int f = 0; f <= 1; f++) { round_info.cout << string(6, ' ') + (f == 0 ? "" : "negated ") - + "formula:" + string(19 - 8 * f, ' '); + + "formula:" + string(19 - 8 * f, ' '); round_info.formulae[f + 2]->print(round_info.cout); round_info.cout << '\n'; @@ -619,9 +702,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 @@ -639,17 +722,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 " @@ -659,8 +743,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 " @@ -673,9 +758,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; } @@ -701,17 +786,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 @@ -739,8 +825,7 @@ void writeFormulaeToFiles() /* ========================================================================= */ void generateBuchiAutomaton (int f, - vector::size_type algorithm_id) + vector::size_type algorithm_id) /* ---------------------------------------------------------------------------- * * Description: Constructs a BuchiAutomaton by invoking an external program @@ -765,11 +850,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]; @@ -780,140 +864,364 @@ 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; + + struct sigaction timeout_sa; + timeout_sa.sa_handler = timeoutHandler; + sigemptyset(&timeout_sa.sa_mask); + timeout_sa.sa_flags = 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); + /* + * Redirect standard output and standard error to files. + */ - exitcode = system(command_line.c_str()); + try + { + openFile(round_info.cout_capture_file->get(), stdout_capture_fileno, + O_CREAT | O_WRONLY | O_TRUNC, 10); - times(&timing_information_end); + 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)) + ")"); + } - 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 + /* 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); + translator_process = fork(); + switch (translator_process) + { + case 0 : /* child */ + close(error_pipe[0]); + + if (setpgid(0, 0) != -1 + && dup2(stdout_capture_fileno, STDOUT_FILENO) != -1 + && dup2(stderr_capture_fileno, STDERR_FILENO) != -1) + execvp(algorithm.parameters[0], algorithm.parameters); + + /* setsid, 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 */ + translator_process = 0; + error_number = errno; + close(error_pipe[0]); + close(error_pipe[1]); + break; + + default : /* parent */ + setpgid(translator_process, translator_process); + + if (configuration.global_options.handle_breaks) + { + /* If lbtt is currently in the foreground (and has a controlling + terminal), transfer the controlling terminal to the translator + process. */ + + const pid_t foreground_pgrp = tcgetpgrp(STDIN_FILENO); + if (foreground_pgrp != -1 && foreground_pgrp == getpgrp()) + { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGTTOU); + sigprocmask(SIG_BLOCK, &mask, 0); + tcsetpgrp(STDIN_FILENO, translator_process); + sigprocmask(SIG_UNBLOCK, &mask, 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(translator_process, &exitcode, 0) == -1) + /* waitpid failed */ + { + error_number = errno; + if (kill(translator_process, 0) == 0) /* child still running */ + { + /* + * 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(-translator_process, sig); + sleep(delay); + if (waitpid(translator_process, &exitcode, WNOHANG) != 0) + { + times(&timing_information_end); + translator_process = 0; + break; + } + if (attempts_to_terminate == 2) + { + sig = SIGKILL; + delay = 5; + } + } + } + else if (errno != EPERM) + translator_process = 0; + } + else /* child exited successfully */ + { + times(&timing_information_end); + translator_process = 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) + { + /* Put lbtt again in the foreground. */ + + if (tcgetpgrp(STDIN_FILENO) != -1) + { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGTTOU); + sigprocmask(SIG_BLOCK, &mask, 0); + tcsetpgrp(STDIN_FILENO, getpgrp()); + sigprocmask(SIG_UNBLOCK, &mask, 0); + } + } + + if (translator_process == 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); + / sysconf(_SC_CLK_TCK); + + break; + } + } + + close(stdout_capture_fileno); + close(stderr_capture_fileno); /* - * Nonzero exit codes from the external program are interpreted as - * errors. In this case, throw an exception indicating that the program - * execution failed. + * If translator_process != 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 (exitcode != 0) + if (translator_process != 0) { - /* - * system() blocks SIGINT and SIGQUIT. If the child was killed - * by such a signal, forward the signal to the current process. - * If lbtt is interactive, SIGINT will be handled as a user - * break. If lbtt is non-interactive, SIGINT will kill lbtt. - * This is what we expect when hitting C-c while lbtt is running. - */ - if (WIFSIGNALED(exitcode) && - (WTERMSIG(exitcode) == SIGINT || WTERMSIG(exitcode) == SIGQUIT)) - raise(WTERMSIG(exitcode)); + stdout_capture_fileno = stderr_capture_fileno = -1; + throw Exception("could not terminate child process"); + } + if (error_number != 0) /* pipe, fork, setsid, 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("Automaton generation aborted due to timeout."); + 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 + const char* signame = strsignal(WTERMSIG(exitcode)); + if (signame != 0) + failure_reason += " (" + string(signame) + ")"; +#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"; - } + + configuration.algorithmString(algorithm_id) + + ", " + + (f == 0 ? "posi" : "nega") + + "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 { @@ -921,95 +1229,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(); + + "Error reading automaton: " + + 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&) - { + + "lbtt internal error: " + + 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 (translator_process != 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 @@ -1030,7 +1347,7 @@ void generateBuchiAutomaton += automaton_stats.number_of_buchi_transitions; final_statistics[algorithm_id].total_number_of_acceptance_sets[f] += automaton_stats.number_of_acceptance_sets; - + if (final_statistics[algorithm_id].total_buchi_generation_time[f] < 0.0 || automaton_stats.buchi_generation_time < 0.0) final_statistics[algorithm_id].total_buchi_generation_time[f] = -1.0; @@ -1041,149 +1358,14 @@ 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, - vector::size_type - algorithm_id) + vector::size_type algorithm_id) /* ---------------------------------------------------------------------------- * * Description: Performs the emptiness check on a ProductAutomaton, i.e., @@ -1207,29 +1389,70 @@ 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 (" + writeToTranscript("User break while generating product automaton (" + configuration.algorithmString(algorithm_id) + ", " + (f == 0 ? "posi" : "nega") + "tive formula)\n"); @@ -1238,31 +1461,37 @@ 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); + } /* ========================================================================= */ void performConsistencyCheck - (vector::size_type - algorithm_id) + (vector::size_type algorithm_id) /* ---------------------------------------------------------------------------- * * Description: Checks the model checking results for consistency for a @@ -1290,8 +1519,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 @@ -1331,10 +1558,10 @@ 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); -} +} /* ========================================================================= */ void compareResults() @@ -1353,14 +1580,13 @@ 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; AutomatonStats* alg_2_stats; - for (vector::size_type - alg_1 = 0; + for (vector::size_type alg_1 = 0; alg_1 < test_results.size(); ++alg_1) { @@ -1368,8 +1594,7 @@ void compareResults() { alg_1_stats = &test_results[alg_1].automaton_stats[counter]; - for (vector - ::size_type alg_2 = alg_1 + 1; + for (vector::size_type alg_2 = alg_1 + 1; alg_2 < test_results.size(); ++alg_2) { @@ -1387,7 +1612,7 @@ void compareResults() unsigned long int dist = alg_1_stats->emptiness_check_result.hammingDistance - (alg_2_stats->emptiness_check_result); + (alg_2_stats->emptiness_check_result); alg_1_stats->cross_comparison_stats[alg_2].first = alg_2_stats->cross_comparison_stats[alg_1].first @@ -1406,9 +1631,9 @@ void compareResults() != alg_2_stats->emptiness_check_result[0]) { (final_statistics[alg_1]. - initial_cross_comparison_mismatches[alg_2])++; + initial_cross_comparison_mismatches[alg_2])++; (final_statistics[alg_2]. - initial_cross_comparison_mismatches[alg_1])++; + initial_cross_comparison_mismatches[alg_1])++; } result = false; @@ -1421,6 +1646,9 @@ void compareResults() } } + IntervalList algorithms; + algorithms.merge(0, round_info.number_of_translators - 1); + if (!result) { round_info.error = true; @@ -1429,13 +1657,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); } /* ========================================================================= */ @@ -1451,26 +1686,27 @@ 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; - for (vector::size_type - alg_1 = 0; + ::Graph::BuchiProduct::clearSatisfiabilityCache(); + + for (vector::size_type alg_1 = 0; alg_1 < round_info.number_of_translators; ++alg_1) { - for (vector::size_type - alg_2 = 0; + for (vector::size_type alg_2 = 0; 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]. @@ -1488,14 +1724,11 @@ void performBuchiIntersectionCheck() if (test_results[alg_1].automaton_stats[0].buchiAutomatonComputed() && test_results[alg_2].automaton_stats[1]. - buchiAutomatonComputed()) + 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 @@ -1505,85 +1738,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; @@ -1592,6 +1755,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]++; @@ -1608,55 +1788,75 @@ 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 " "emptiness check"); round_info.transcript_file << string(8, ' ') + "(+) " - + configuration.algorithmString(alg_1) - + ", (-) " - + configuration.algorithmString(alg_2) - + "\n\n"; - - if (automaton_intersection != 0) - { - delete automaton_intersection; - automaton_intersection = 0; + + configuration.algorithmString(alg_1) + + ", (-) " + + configuration.algorithmString(alg_2) + + "\n\n"; } throw; } - catch (const bad_alloc&) + catch (const ::Graph::Product< ::Graph::BuchiProduct>::SizeException&) { - if (automaton_intersection != 0) - { - delete automaton_intersection; - automaton_intersection = 0; - } - - if (!printText(" out of memory\n", 4)) - printText("[Out of memory: (+) " + if (!printText(": aborted (product may be too large)", 4)) + printText(" [Product may be too large: (+) " + configuration.algorithmString(alg_1) + ", (-) " + configuration.algorithmString(alg_2) - + "]\n", + + "]", 2, 6); + printText("\n", 2); if (round_info.transcript_file.is_open()) + { + writeToTranscript("Automata intersection emptiness check aborted " + "(product may be too large)"); + round_info.transcript_file << string(8, ' ') + "(+) " + + configuration.algorithmString(alg_1) + + ", (-) " + + configuration.algorithmString(alg_2) + + "\n\n"; + } + } + catch (const bad_alloc&) + { + if (!printText(": aborted (out of memory)", 4)) + printText(" [Out of memory: (+) " + + configuration.algorithmString(alg_1) + + ", (-) " + + configuration.algorithmString(alg_2) + + "]", + 2, + 6); + printText("\n", 2); + + if (round_info.transcript_file.is_open()) + { writeToTranscript("Out of memory during Büchi automata " "intersection emptiness check"); round_info.transcript_file << string(8, ' ') + "(+) " - + configuration.algorithmString(alg_1) - + ", (-) " - + configuration.algorithmString(alg_2) - + "\n\n"; + + configuration.algorithmString(alg_1) + + ", (-) " + + configuration.algorithmString(alg_2) + + "\n\n"; + } } } } + IntervalList algorithms; + algorithms.merge(0, round_info.number_of_translators - 1); + if (!result) { round_info.error = true; @@ -1665,13 +1865,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..74b9cdfd1 100644 --- a/lbtt/src/TestOperations.h +++ b/lbtt/src/TestOperations.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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 */ @@ -90,32 +95,25 @@ void writeFormulaeToFiles(); /* Writes LTL formulas */ void generateBuchiAutomaton /* Generates a Büchi */ (int f, /* automaton from a LTL */ - vector /* given file, using a */ - ::size_type /* given LTL-to-Büchi */ - algorithm_id); /* translation algorithm + vector /* formula stored in a */ + ::size_type /* given file, using a */ + algorithm_id); /* given LTL-to-Büchi + * translation algorithm * 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 + vector /* automaton. */ ::size_type algorithm_id); void performConsistencyCheck /* Performs a */ - (vector /* the test results */ - ::size_type /* for a formula and its */ - algorithm_id); /* negation. */ + (vector /* consistency check on */ + ::size_type /* the test results */ + algorithm_id); /* for a formula and its + * negation. + */ void compareResults(); /* Compares the model * checking results diff --git a/lbtt/src/TestRoundInfo.h b/lbtt/src/TestRoundInfo.h index 23351792a..8b396b9d7 100644 --- a/lbtt/src/TestRoundInfo.h +++ b/lbtt/src/TestRoundInfo.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,9 +28,9 @@ #include "LbttAlloc.h" #include "Exception.h" #include "LtlFormula.h" -#include "ProductAutomaton.h" #include "PathIterator.h" #include "StateSpace.h" +#include "TempFsysName.h" using namespace std; @@ -55,7 +55,11 @@ public: * stream for messages. */ - ifstream formula_input_file; /* Stream for reading input + istream* formula_input_stream; /* Stream for reading input + * formulae. + */ + + ifstream formula_input_file; /* File for reading input * formulae. */ @@ -90,6 +94,10 @@ public: * current round. */ + bool all_tests_successful; /* True if no errors have + * occurred during testing. + */ + bool skip; /* True if the current * round is to be skipped. */ @@ -122,11 +130,6 @@ public: * paths as state spaces. */ - const Graph::ProductAutomaton* product_automaton; /* Pointer to the product - * automaton used in the - * current test round. - */ - unsigned long int real_emptiness_check_size; /* Number of states in the * state space where the * emptiness check should @@ -150,9 +153,9 @@ public: * current round. */ - vector /* current round: */ - formulae; /* formulae[0]: + vector formulae; /* Formulae used in the + * current round: + * formulae[0]: * positive formula in * negation normal * form @@ -168,7 +171,7 @@ public: * generated */ - vector formula_in_file; /* The values in this + vector formula_in_file; /* The values in this * vector will be set to * true when the * corresponding @@ -181,10 +184,10 @@ public: * formula. */ - char formula_file_name[2][L_tmpnam + 1]; /* Storage space for the */ - char automaton_file_name[L_tmpnam + 1]; /* names of several */ - char cout_capture_file[L_tmpnam + 1]; /* temporary files. */ - char cerr_capture_file[L_tmpnam + 1]; + TempFsysName* formula_file_name[2]; /* Names for temporary */ + TempFsysName* automaton_file_name; /* files. */ + TempFsysName* cout_capture_file; + TempFsysName* cerr_capture_file; private: TestRoundInfo(const TestRoundInfo& info); /* Prevent copying and */ @@ -206,14 +209,15 @@ private: inline TestRoundInfo::TestRoundInfo() : cout(&std::cout, ios::failbit | ios::badbit), number_of_translators(0), current_round(1), next_round_to_run(1), next_round_to_stop(1), - error_report_round(0), error(false), skip(false), abort(false), - num_generated_statespaces(0), total_statespace_states(0), - total_statespace_transitions(0), num_processed_formulae(0), - fresh_statespace(false), statespace(0), path_iterator(0), - product_automaton(0), real_emptiness_check_size(0), + error_report_round(0), error(false), all_tests_successful(true), + skip(false), abort(false), num_generated_statespaces(0), + total_statespace_states(0), total_statespace_transitions(0), + num_processed_formulae(0), fresh_statespace(false), statespace(0), + path_iterator(0), real_emptiness_check_size(0), next_round_to_change_statespace(1), next_round_to_change_formula(1), fresh_formula(false), formulae(4, static_cast(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 +229,7 @@ inline TestRoundInfo::TestRoundInfo() : * * ------------------------------------------------------------------------- */ { + formula_file_name[0] = formula_file_name[1] = 0; } /* ========================================================================= */ diff --git a/lbtt/src/TestStatistics.cc b/lbtt/src/TestStatistics.cc index b791e27b4..053df05b0 100644 --- a/lbtt/src/TestStatistics.cc +++ b/lbtt/src/TestStatistics.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,10 +17,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #include "TestStatistics.h" @@ -54,8 +50,7 @@ void AlgorithmTestResults::emptinessReset() automaton_stats[i].emptiness_check_result.clear(); automaton_stats[i].emptiness_check_performed = false; - for (vector::iterator it + for (vector::iterator it = automaton_stats[i].cross_comparison_stats.begin(); it != automaton_stats[i].cross_comparison_stats.end(); ++it) @@ -91,7 +86,7 @@ void AlgorithmTestResults::fullReset() automaton_stats[i].number_of_msccs = 0; automaton_stats[i].buchi_generation_time = 0.0; - for (vector::iterator it + for (vector::iterator it = automaton_stats[i].buchi_intersection_check_stats.begin(); it != automaton_stats[i].buchi_intersection_check_stats.end(); ++it) diff --git a/lbtt/src/TestStatistics.h b/lbtt/src/TestStatistics.h index 70cc6110e..e6a16500c 100644 --- a/lbtt/src/TestStatistics.h +++ b/lbtt/src/TestStatistics.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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; /****************************************************************************** * @@ -43,9 +43,8 @@ using Graph::ProductAutomaton; struct AutomatonStats { explicit AutomatonStats /* Constructor. */ - (vector - ::size_type number_of_algorithms, + (vector + ::size_type number_of_algorithms, StateSpace::size_type max_statespace_size); /* default copy constructor */ @@ -107,7 +106,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 @@ -127,9 +126,9 @@ struct AutomatonStats typedef pair CrossComparisonStats; - vector /* cross-comparison */ - cross_comparison_stats; /* results. The `first' + vector /* Emptiness check */ + cross_comparison_stats; /* cross-comparison + * results. The `first' * element of the pair * tells whether a cross- * comparison with a given @@ -142,8 +141,8 @@ struct AutomatonStats * differ. */ - vector /* Büchi automaton */ - buchi_intersection_check_stats; /* intersection + vector buchi_intersection_check_stats; /* Büchi automaton + * intersection * emptiness check * results. The elements * of the vector tell @@ -173,8 +172,7 @@ struct AutomatonStats struct AlgorithmTestResults { explicit AlgorithmTestResults /* Constructor. */ - (vector + (vector ::size_type number_of_algorithms, StateSpace::size_type max_statespace_size); @@ -214,8 +212,8 @@ struct AlgorithmTestResults * check. */ - vector /* A two-element vector */ - automaton_stats; /* storing test results + vector automaton_stats; /* A two-element vector + * storing test results * for an algorithm. */ }; @@ -232,8 +230,7 @@ struct AlgorithmTestResults struct TestStatistics { explicit TestStatistics /* Constructor. */ - (vector::size_type + (vector::size_type number_of_algorithms); /* default copy constructor */ @@ -270,29 +267,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,36 +289,37 @@ struct TestStatistics * automata. */ - unsigned long int /* Total number of */ - total_number_of_product_states[2]; /* states in all the + BIGUINT total_number_of_product_states[2]; /* Total number of states + * in all the generated + * product automata. + */ + + BIGUINT total_number_of_product_transitions[2]; /* Total number of + * transitions in all the * generated product * automata. */ - unsigned long int /* Total number of */ - total_number_of_product_transitions[2]; /* transitions in all the - * generated product - * automata. + vector /* Number of failed */ + cross_comparison_mismatches; /* result cross- + * comparisons. */ - vector /* result cross- */ - cross_comparison_mismatches; /* comparisons. */ - - vector /* result cross- */ - initial_cross_comparison_mismatches; /* comparisons in the + vector /* Number of failed */ + initial_cross_comparison_mismatches; /* result cross- + * comparisons in the * initial state of the * state space. */ - vector /* cross-comparisons */ - cross_comparisons_performed; /* performed. */ + vector /* Number of result */ + cross_comparisons_performed; /* cross-comparisons + * performed. + */ - vector /* Büchi automaton */ - buchi_intersection_check_failures; /* emptiness checks + vector /* Number of failed */ + buchi_intersection_check_failures; /* Büchi automaton + * emptiness checks * against the automata * constructed from the * negated formula @@ -337,9 +327,9 @@ struct TestStatistics * algorithms. */ - vector /* automaton emptiness */ - buchi_intersection_checks_performed; /* checks performed + vector /* Number of Büchi */ + buchi_intersection_checks_performed; /* automaton emptiness + * checks performed * against the automata * constructed from the * negated formula using @@ -357,9 +347,7 @@ struct TestStatistics /* ========================================================================= */ inline AutomatonStats::AutomatonStats - (vector::size_type - number_of_algorithms, + (vector::size_type number_of_algorithms, StateSpace::size_type max_statespace_size) : buchi_automaton(0), number_of_buchi_states(0), number_of_buchi_transitions(0), number_of_acceptance_sets(0), @@ -481,9 +469,7 @@ AutomatonStats::buchiIntersectionCheckPerformed(unsigned long int algorithm) /* ========================================================================= */ inline AlgorithmTestResults::AlgorithmTestResults - (vector::size_type - number_of_algorithms, + (vector::size_type number_of_algorithms, StateSpace::size_type max_statespace_size) : consistency_check_result(-1), consistency_check_comparisons(0), failed_consistency_check_comparisons(0), @@ -528,8 +514,7 @@ inline AlgorithmTestResults::~AlgorithmTestResults() /* ========================================================================= */ inline TestStatistics::TestStatistics - (vector::size_type - number_of_algorithms) : + (vector::size_type number_of_algorithms) : consistency_check_failures(0), consistency_checks_performed(0), cross_comparison_mismatches(number_of_algorithms, 0), initial_cross_comparison_mismatches(number_of_algorithms, 0), @@ -556,7 +541,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/TranslatorInterface.h b/lbtt/src/TranslatorInterface.h index 8053c5bac..eca8e172d 100644 --- a/lbtt/src/TranslatorInterface.h +++ b/lbtt/src/TranslatorInterface.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lbtt/src/UserCommandReader.cc b/lbtt/src/UserCommandReader.cc index 6365784f3..051c56306 100644 --- a/lbtt/src/UserCommandReader.cc +++ b/lbtt/src/UserCommandReader.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,10 +17,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #include #include #include @@ -32,7 +28,6 @@ #include #endif /* HAVE_SSTREAM */ #include "DispUtil.h" -#include "ProductAutomaton.h" #include "SharedTestData.h" #include "StatDisplay.h" #include "StringUtil.h" @@ -50,6 +45,13 @@ #include #endif /* HAVE_READLINE */ +#ifdef HAVE_ISATTY +#ifdef HAVE_UNISTD_H +#include +#endif /* HAVE_UNISTD_H */ +#endif /* HAVE_ISATTY */ + + /****************************************************************************** * @@ -84,7 +86,7 @@ void executeUserCommands() * ------------------------------------------------------------------------ */ { string input_line; - vector input_tokens; + vector input_tokens; TokenType token; bool formula_type = true; @@ -115,9 +117,6 @@ void executeUserCommands() { #endif /* HAVE_READLINE */ - ProductAutomaton* product_automaton = 0; - pair last_computed_product_automaton; - signal(SIGPIPE, SIG_IGN); while (1) @@ -131,47 +130,86 @@ void executeUserCommands() input_line = line; else { - round_info.cout << '\n'; - round_info.cout.flush(); - } #else round_info.cout << prompt; round_info.cout.flush(); getline(cin, input_line, '\n'); if (cin.eof()) { - round_info.cout << '\n'; - round_info.cout.flush(); cin.clear(); +#endif /* HAVE_READLINE */ +#ifdef HAVE_ISATTY + /* + * If standard input is not bound to a terminal, act on EOF as if the + * `continue' command had been issued. Otherwise act as if an empty + * line was given as input. + */ + if (!isatty(STDIN_FILENO)) + input_line = "continue"; + else + { + round_info.cout << '\n'; + round_info.cout.flush(); + } +#else + input_line = "continue"; + round_info.cout << input_line << '\n'; + round_info.cout.flush(); +#endif /* HAVE_ISATTY */ + } + +#ifdef HAVE_ISATTY + if (!isatty(STDIN_FILENO)) + { + round_info.cout << input_line << '\n'; + round_info.cout.flush(); + } +#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 - 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) @@ -192,9 +230,7 @@ void executeUserCommands() { bool all_algorithms_disabled = true; - for (vector - ::const_iterator + for (vector::const_iterator algorithm = configuration.algorithms.begin(); algorithm != configuration.algorithms.end(); ++algorithm) @@ -379,8 +415,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"; @@ -403,8 +438,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" @@ -431,8 +466,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"; @@ -474,8 +508,8 @@ void executeUserCommands() break; default : - throw CommandErrorException("Unknown command (`" - + input_tokens[0] + "')."); + throw CommandErrorException("Unknown command (`" + input_tokens[0] + + "')."); } if (output_string != 0) @@ -486,21 +520,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(); } } @@ -533,16 +563,6 @@ void executeUserCommands() } } - if (product_automaton != 0) - { - ::DispUtil::printText - ("", 4, 2); - - delete product_automaton; - - ::DispUtil::printText(" ok\n", 4); - } - #ifdef HAVE_READLINE } catch (...) @@ -555,7 +575,7 @@ void executeUserCommands() #endif /* HAVE_READLINE */ signal(SIGPIPE, SIG_DFL); -} +} /* ========================================================================= */ TokenType parseCommand(const string& token) @@ -570,19 +590,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; @@ -679,8 +686,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' : @@ -760,6 +778,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; @@ -770,19 +793,12 @@ TokenType parseCommand(const string& token) throw CommandErrorException("Ambiguous command."); return token_type; - -#ifdef __GNUC__ -#if __GNUC__ < 3 -#undef compare -#endif -#endif } /* ========================================================================= */ void verifyArgumentCount - (const vector& command, - vector::size_type min_arg_count, - vector::size_type max_arg_count) + (const vector& command, vector::size_type min_arg_count, + vector::size_type max_arg_count) /* ---------------------------------------------------------------------------- * * Description: Verifies that the number of arguments given for a user @@ -805,8 +821,7 @@ void verifyArgumentCount } /* ========================================================================= */ -pair parseRedirection - (vector& input_tokens) +pair parseRedirection(vector& input_tokens) /* ---------------------------------------------------------------------------- * * Description: Tests whether the last argument to a user command specifies @@ -840,13 +855,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(); } } @@ -855,10 +870,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(); @@ -870,7 +884,7 @@ pair parseRedirection } /* ========================================================================= */ -bool parseFormulaType(vector& input_tokens) +bool parseFormulaType(vector& input_tokens) /* ---------------------------------------------------------------------------- * * Description: Tests whether the first argument of a command specifies a @@ -894,8 +908,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/UserCommandReader.h b/lbtt/src/UserCommandReader.h index dfe489291..b9a510519 100644 --- a/lbtt/src/UserCommandReader.h +++ b/lbtt/src/UserCommandReader.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -65,22 +65,19 @@ TokenType parseCommand(const string& token); /* Translates a command */ void verifyArgumentCount /* Checks that the */ - (const vector& /* number of arguments */ - arguments, /* for a command is */ - vector::size_type /* within given limits. */ - min_arg_count, - vector::size_type - max_arg_count); + (const vector& arguments, /* number of arguments */ + vector::size_type min_arg_count, /* for a command is */ + vector::size_type max_arg_count); /* within given limits. */ pair parseRedirection /* Checks whether an */ - (vector& input_tokens); /* user command given + (vector& input_tokens); /* user command given * will require * redirecting its * output to a file. */ bool parseFormulaType /* Checks whether an */ - (vector& input_tokens); /* user command + (vector& input_tokens); /* user command * specified a positive * or a negative * formula. diff --git a/lbtt/src/UserCommands.cc b/lbtt/src/UserCommands.cc index ae5a6630a..08980d028 100644 --- a/lbtt/src/UserCommands.cc +++ b/lbtt/src/UserCommands.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,16 +17,15 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef __GNUC__ -#pragma implementation -#endif /* __GNUC__ */ - #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" @@ -49,84 +48,95 @@ 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::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::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()) + + ")."); } } @@ -162,64 +172,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 @@ -236,14 +195,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. * @@ -259,33 +210,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; @@ -296,17 +250,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)) @@ -316,7 +259,7 @@ void printCrossComparisonAnalysisResults "performed between " + configuration.algorithmString(algorithm1) + " and " + configuration.algorithmString(algorithm2) - + ".\n", + + ".", 78); return; } @@ -328,7 +271,7 @@ void printCrossComparisonAnalysisResults "results given by " + configuration.algorithmString(algorithm1) + " and " - + configuration.algorithmString(algorithm2) + ".\n", + + configuration.algorithmString(algorithm2) + ".", 78); return; } @@ -378,7 +321,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) { @@ -390,7 +334,7 @@ void printCrossComparisonAnalysisResults + configuration.algorithmString(algorithm2) + " in state " + toString(state) - + " of the state space.\n", + + " of the state space.", 78); return; } @@ -405,7 +349,7 @@ void printCrossComparisonAnalysisResults + configuration.algorithmString(algorithm2) + " in state " + toString(state) - + " of the state space.\n", + + " of the state space.", 78); return; } @@ -424,40 +368,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 (...) { @@ -466,71 +407,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) @@ -542,7 +439,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, ' ') @@ -561,8 +458,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); @@ -577,20 +474,24 @@ 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); + } } /* ========================================================================= */ void printConsistencyAnalysisResults - (ostream& stream, int indent, - const vector& input_tokens) + (ostream& stream, int indent, const vector& input_tokens) /* ---------------------------------------------------------------------------- * * Description: Implements the user command `consistencyanalysis', i.e., @@ -614,10 +515,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) { @@ -625,7 +523,7 @@ void printConsistencyAnalysisResults "Model checking result consistency check was not performed " "on implementation " + configuration.algorithmString(algorithm_id) - + ".\n", + + ".", 78); return; } @@ -635,7 +533,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; } @@ -675,7 +573,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) { @@ -684,7 +583,7 @@ void printConsistencyAnalysisResults "performed on implementation " + configuration.algorithmString(algorithm_id) + " in state " + toString(state) + " of the state " - "space.\n", + "space.", 78); return; } @@ -698,17 +597,15 @@ 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; - map, ALLOC(StateSpace::size_type) > - ordering; + vector path; + StateSpace::Path prefix, cycle; + map ordering; StateSpace::size_type state_count = 0; StateSpace::size_type loop_state; @@ -732,10 +629,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)); @@ -745,8 +642,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); @@ -755,14 +651,13 @@ 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 @@ -770,11 +665,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. * @@ -787,10 +682,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]. @@ -810,7 +709,7 @@ void printAutomatonAnalysisResults : " (positive formula) and " + configuration.algorithmString(algorithm2) + " (negative formula)") - + ".\n", + + ".", 78); return; @@ -842,299 +741,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. - */ - - deque prefix; - deque cycle; - vector path; - - while (!execution.first.empty()) - { - prefix.push_back(execution.first.front().first); - path.push_back(execution.first.front().first); - execution.first.pop_front(); - } - - while (!execution.second.empty()) - { - cycle.push_back(execution.second.front().first); - path.push_back(execution.second.front().first); - execution.second.pop_front(); - } - - /* - * Construct an execution accepted by both of the automata. This is done - * by giving suitable truth assignments for the atomic propositions in - * selected states of the state space to which the intersection automaton - * was converted. - */ - - GraphEdgeContainer::const_iterator transition; - - path.push_back(cycle.front()); - - for (vector - ::size_type state = 0; - state + 1 < path.size(); - state++) - { - for (transition = (*a)[path[state]].edges().begin(); - (*transition)->targetNode() != path[state + 1]; - ++transition) - ; - - automaton_as_statespace[path[state]].positiveAtoms() - = static_cast(*transition) - ->guard().findPropositionalModel - (configuration.formula_options.formula_generator. - number_of_available_variables); - } - - path.pop_back(); - - 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; - - const deque* - original_execution_states; - deque* - new_execution_states; - - for (int i = 0; i < 2; i++) - { - aut_prefix.clear(); - aut_cycle.clear(); - - for (int j = 0; j < 2; j++) - { - if (j == 0) - { - original_execution_states = &prefix; - new_execution_states = &aut_prefix; - } - else - { - original_execution_states = &cycle; - new_execution_states = &aut_cycle; - } - - for (deque::const_iterator - execution_state = original_execution_states->begin(); - execution_state != original_execution_states->end(); - ++execution_state) - { - new_execution_states->push_back - (i == 0 - ? intersection_state_mapping[*execution_state].first - : intersection_state_mapping[*execution_state].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)); - } - - 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, - prefix.size()); - - 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. * @@ -1142,96 +912,93 @@ 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(); } /* ========================================================================= */ void printAcceptingCycle (ostream& stream, int indent, - vector::size_type - algorithm_id, - const deque& - prefix, - const deque& - cycle, - const BuchiAutomaton& automaton) + vector::size_type algorithm_id, + 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. * @@ -1239,10 +1006,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 " @@ -1252,84 +1015,104 @@ 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(); } /* ========================================================================= */ void printBuchiAutomaton (ostream& stream, int indent, bool formula_type, - vector& input_tokens, - Graph::GraphOutputFormat fmt) + vector& input_tokens, Graph::GraphOutputFormat fmt) /* ---------------------------------------------------------------------------- * * Description: Implements the user command `buchi', i.e., writes information @@ -1352,16 +1135,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()) { @@ -1370,7 +1154,7 @@ void printBuchiAutomaton + configuration.algorithmString(algorithm) + " for the formula `" + toString(*round_info.formulae[formula]) - + "'.\n", + + "'.", 78); return; } @@ -1400,22 +1184,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"; @@ -1427,14 +1216,13 @@ void printBuchiAutomaton else if (fmt == Graph::DOT) automaton->print(stream, indent, Graph::DOT); - estream << '\n'; estream.flush(); } /* ========================================================================= */ void evaluateFormula - (ostream& stream, int indent, bool formula_type, - vector& input_tokens) + (ostream& stream, int indent, bool formula_type, + vector& input_tokens) /* ---------------------------------------------------------------------------- * * Description: Implements the user command `evaluate', i.e., tells whether @@ -1457,13 +1245,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) @@ -1473,55 +1258,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. @@ -1532,8 +1278,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 "; @@ -1545,35 +1301,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] @@ -1581,28 +1328,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 @@ -1615,6 +1349,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. * @@ -1626,23 +1363,35 @@ 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(); } /* ========================================================================= */ void printCommandHelp - (ostream& stream, int indent, - const vector& input_tokens) + (ostream& stream, int indent, const vector& input_tokens) /* ---------------------------------------------------------------------------- * * Description: Implements the user command `help', i.e., gives instructions @@ -1664,16 +1413,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 " @@ -1779,21 +1531,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; @@ -1837,14 +1589,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; @@ -1918,6 +1665,7 @@ void printCommandHelp "evaluate\n" "formula\n" "help\n" + "implementations\n" "inconsistencies\n" "quit\n" "resultanalysis\n" @@ -1925,6 +1673,7 @@ void printCommandHelp "skip\n" "statespace\n" "statistics\n" + "translators\n" "verbosity\n", 78); @@ -1949,13 +1698,12 @@ void printCommandHelp break; } - estream << '\n'; estream.flush(); } /* ========================================================================= */ void printInconsistencies - (ostream& stream, int indent, vector& input_tokens) + (ostream& stream, int indent, vector& input_tokens) /* ---------------------------------------------------------------------------- * * Description: Implements the user command `inconsistencies', i.e., lists @@ -1975,14 +1723,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"; @@ -1996,23 +1748,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) @@ -2060,13 +1802,12 @@ void printInconsistencies } } - estream << '\n'; estream.flush(); } /* ========================================================================= */ void printTestResults - (ostream& stream, int indent, vector& input_tokens) + (ostream& stream, int indent, vector& input_tokens) /* ---------------------------------------------------------------------------- * * Description: Implements the user command `results', i.e., displays the @@ -2085,48 +1826,47 @@ 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(); } /* ========================================================================= */ void printStateSpace - (ostream& stream, int indent, vector& input_tokens, + (ostream& stream, int indent, vector& input_tokens, Graph::GraphOutputFormat fmt) /* ---------------------------------------------------------------------------- * @@ -2146,8 +1886,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) @@ -2160,22 +1899,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)" @@ -2190,12 +1935,11 @@ void printStateSpace else if (fmt == Graph::DOT) round_info.statespace->print(stream, indent, Graph::DOT); - estream << '\n'; estream.flush(); } /* ========================================================================= */ -void changeVerbosity(const vector& input_tokens) +void changeVerbosity(const vector& input_tokens) /* ---------------------------------------------------------------------------- * * Description: Implements the user command `verbosity', i.e., displays or @@ -2232,8 +1976,7 @@ void changeVerbosity(const vector& input_tokens) } /* ========================================================================= */ -void changeAlgorithmState - (vector& input_tokens, bool enable) +void changeAlgorithmState(vector& input_tokens, bool enable) /* ---------------------------------------------------------------------------- * * Description: Changes the enabledness of a set of algorithms used in the @@ -2248,31 +1991,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..51ed74ef5 100644 --- a/lbtt/src/UserCommands.h +++ b/lbtt/src/UserCommands.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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,119 +49,102 @@ 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); + const vector& input_tokens); /* implementations. */ void printConsistencyAnalysisResults /* Analyzes a */ (ostream& stream, int indent, /* contradicition in the */ - const vector& /* model checking result */ - input_tokens); /* consistency check for + const vector& input_tokens); /* model checking result + * consistency check for * an implementation. */ 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& input_tokens); /* Büchi automata + * 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 */ (ostream& stream, int indent, /* a single automaton */ - vector + vector /* execution. */ ::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 */ bool formula_type, /* automaton. */ - vector& input_tokens, + vector& input_tokens, Graph::GraphOutputFormat fmt); void evaluateFormula /* Displays information */ (ostream& stream, int indent, /* about existence of */ bool formula_type, /* accepting system */ - vector& input_tokens); /* executions. */ + vector& input_tokens); /* executions. */ 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. */ - const vector& - input_tokens); + const vector& input_tokens); void printInconsistencies /* Lists the system */ (ostream& stream, int indent, /* states failing the */ - vector& input_tokens); /* consistency check + vector& input_tokens); /* consistency check * for an algorihm. */ void printTestResults /* Displays the test */ (ostream& stream, int indent, /* results of the last */ - vector& input_tokens); /* round performed. */ + vector& input_tokens); /* round performed. */ void printStateSpace /* Displays information */ (ostream& stream, int indent, /* about a state space. */ - vector& input_tokens, + vector& input_tokens, Graph::GraphOutputFormat fmt); void changeVerbosity /* Displays or changes */ - (const vector& /* the verbosity of */ - input_tokens); /* output. */ + (const vector& input_tokens); /* the verbosity of + * output. + */ void changeAlgorithmState /* Enables or disables a */ - (vector& input_tokens, /* set of algorithms */ - bool enable); /* used in the tests. */ + (vector& input_tokens, bool enable); /* set of algorithms + * used in the tests. + */ } diff --git a/lbtt/src/main.cc b/lbtt/src/main.cc index 197fbe171..4c4093da2 100644 --- a/lbtt/src/main.cc +++ b/lbtt/src/main.cc @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -21,6 +21,9 @@ #include #include #include +#ifdef HAVE_SYS_TYPES_H +#include +#endif /* HAVE_SYS_TYPES_H */ #include #include #ifdef HAVE_READLINE @@ -28,6 +31,11 @@ #include #include #endif /* HAVE_READLINE */ +#ifdef HAVE_ISATTY +#ifdef HAVE_UNISTD_H +#include +#endif /* HAVE_UNISTD_H */ +#endif /* HAVE_ISATTY */ #include "LbttAlloc.h" #include "Configuration.h" #include "DispUtil.h" @@ -36,6 +44,7 @@ #include "Random.h" #include "SharedTestData.h" #include "StatDisplay.h" +#include "TempFsysName.h" #include "TestOperations.h" #include "TestRoundInfo.h" #include "TestStatistics.h" @@ -43,17 +52,6 @@ using namespace std; -/****************************************************************************** - * - * Handler for the SIGINT signal. - * - *****************************************************************************/ - -RETSIGTYPE breakHandler(int) -{ - user_break = true; -} - /****************************************************************************** @@ -92,12 +90,12 @@ TestRoundInfo round_info; /* Data structure for * round. */ -vector /* individual algorithm. */ - test_results; +vector test_results; /* Test results for each + * individual algorithm. + */ -vector /* Overall test */ - final_statistics; /* statistics for each +vector final_statistics; /* Overall test + * statistics for each * algorithm. */ @@ -105,13 +103,124 @@ 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. + * + *****************************************************************************/ + +pid_t translator_process = 0; /* Process group for translator process */ + +static void abortHandler(int signum) +{ + deallocateTempFilenames(); + if (translator_process != 0 && kill(translator_process, 0) == 0) + kill(-translator_process, SIGTERM); + 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. * *****************************************************************************/ -int testLoop() +bool testLoop() { using namespace DispUtil; using namespace SharedTestData; @@ -119,9 +228,6 @@ int testLoop() using namespace StringUtil; using namespace TestOperations; - /* Return code. Will be set to 1 if any of the test fails. */ - int exit_status = 0; - const Configuration::GlobalConfiguration& global_options = configuration.global_options; @@ -139,13 +245,6 @@ int 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. @@ -173,9 +272,9 @@ int testLoop() try { round_info.transcript_file << "lbtt " PACKAGE_VERSION - " error log file, created on " - + string(ctime(¤t_time)) - + '\n'; + " error log file, created on " + + string(ctime(¤t_time)) + + '\n'; configuration.print(round_info.transcript_file); } @@ -187,16 +286,24 @@ int testLoop() /* * If a formula file name was given in the configuration, open the file for - * reading. + * reading. The special filename "-" refers to the standard input. */ try { if (!global_options.formula_input_filename.empty()) - openFile(global_options.formula_input_filename.c_str(), - round_info.formula_input_file, - ios::in, - 0); + { + if (global_options.formula_input_filename == "-") + round_info.formula_input_stream = &cin; + else + { + openFile(global_options.formula_input_filename.c_str(), + round_info.formula_input_file, + ios::in, + 0); + round_info.formula_input_stream = &round_info.formula_input_file; + } + } } catch (const FileOpenException& e) { @@ -224,19 +331,6 @@ int 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 @@ -265,30 +359,18 @@ int testLoop() for (round_info.current_round = 1; !round_info.abort - && round_info.current_round <= global_options.number_of_rounds; + && round_info.current_round <= global_options.number_of_rounds; ++round_info.current_round) { user_break = false; round_info.error = false; round_info.skip = (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 { @@ -299,7 +381,7 @@ int testLoop() round_info.fresh_statespace = ((global_options.do_comp_test || global_options.do_cons_test) && round_info.next_round_to_change_statespace - == round_info.current_round); + == round_info.current_round); if (round_info.fresh_statespace) { @@ -307,13 +389,13 @@ int testLoop() seed48(statespace_random_state); for (int i = 0; i < 3; i++) statespace_random_state[i] = static_cast - (LRAND(0, LONG_MAX)); + (LRAND(0, LONG_MAX)); #else SRAND(global_options.statespace_random_seed); configuration.global_options.statespace_random_seed = LRAND(0, RAND_MAX); #endif /* HAVE_RAND48 */ - + if (global_options.statespace_change_interval == 0) round_info.next_round_to_change_statespace = global_options.number_of_rounds + 1; @@ -321,8 +403,7 @@ int testLoop() round_info.next_round_to_change_statespace += global_options.statespace_change_interval; - for (vector - ::iterator it = test_results.begin(); + for (vector::iterator it = test_results.begin(); it != test_results.end(); ++it) it->emptinessReset(); @@ -352,7 +433,7 @@ int testLoop() */ round_info.fresh_formula - = (round_info.next_round_to_change_formula + = (round_info.next_round_to_change_formula == round_info.current_round); if (round_info.fresh_formula) @@ -375,8 +456,7 @@ int testLoop() round_info.formula_in_file[0] = round_info.formula_in_file[1] = false; - for (vector - ::iterator it = test_results.begin(); + for (vector::iterator it = test_results.begin(); it != test_results.end(); ++it) it->fullReset(); @@ -387,8 +467,8 @@ int testLoop() { try { - generateFormulae(round_info.formula_input_file.is_open() - ? &round_info.formula_input_file + generateFormulae(!global_options.formula_input_filename.empty() + ? round_info.formula_input_stream : 0); } catch (const FormulaGenerationException&) @@ -402,7 +482,7 @@ int testLoop() if (user_break) { - printText("[User break]\n\n", 2, 4); + printText("[User break]\n\n", 1, 4); throw UserBreakException(); } @@ -417,12 +497,17 @@ int 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; @@ -434,81 +519,61 @@ int 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&) @@ -544,7 +609,13 @@ int 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) @@ -556,10 +627,7 @@ int 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(); } @@ -590,33 +658,22 @@ int testLoop() round_info.next_round_to_stop = round_info.current_round; } - if (round_info.error) - exit_status = 1; - /* * Determine from the program configuration and the error status whether * 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) @@ -628,8 +685,7 @@ int testLoop() ::Ltl::LtlFormula::destruct(round_info.formulae[f]); } - for (vector - ::iterator it = test_results.begin(); + for (vector::iterator it = test_results.begin(); it != test_results.end(); ++it) it->fullReset(); @@ -661,19 +717,19 @@ int testLoop() time(¤t_time); round_info.transcript_file << "lbtt error log closed on " - + string(ctime(¤t_time)) + + string(ctime(¤t_time)) << endl; 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 exit_status; + return round_info.all_tests_successful; } @@ -686,7 +742,7 @@ int testLoop() int main(int argc, char* argv[]) { - try + try { configuration.read(argc, argv); } @@ -697,15 +753,35 @@ int main(int argc, char* argv[]) cerr << ":" << configuration.global_options.cfg_filename << ":" << e.line_info; cerr << ": " << e.what() << endl; - exit(-1); + exit(2); } +#ifdef HAVE_ISATTY + if (configuration.global_options.formula_input_filename == "-" + && !isatty(STDIN_FILENO)) + { + configuration.global_options.interactive = Configuration::NEVER; + configuration.global_options.handle_breaks = false; + } +#endif /* HAVE_ISATTY */ + if (configuration.global_options.verbosity >= 3) configuration.print(cout); user_break = false; - if (configuration.global_options.interactive != Configuration::NEVER) - 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; @@ -715,18 +791,28 @@ int main(int argc, char* argv[]) using_history(); #endif /* HAVE_READLINE */ - try + try { - return 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 a1a6cc0a0..173f796dc 100644 --- a/lbtt/src/translate.cc +++ b/lbtt/src/translate.cc @@ -180,11 +180,11 @@ int main(int argc, char** argv) case OPT_VERSION : cout << "lbtt-translate " PACKAGE_VERSION "\n" - "lbtt-translate is free software; you may change and " - "redistribute it under the\n" - "terms of the GNU General Public License. lbtt-translate " - "comes with NO WARRANTY.\n" - "See the file COPYING for details.\n"; + "lbtt-translate is free software; you may change and " + "redistribute it under the\n" + "terms of the GNU General Public License. lbtt-translate " + "comes with NO WARRANTY.\n" + "See the file COPYING for details.\n"; exit(0); break; diff --git a/lbtt/src/translate.h b/lbtt/src/translate.h index b0fb7e53f..af028f4f4 100644 --- a/lbtt/src/translate.h +++ b/lbtt/src/translate.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 1999, 2000, 2001, 2002 - * Heikki Tauriainen + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + * Heikki Tauriainen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,10 +20,6 @@ #ifndef TRANSLATE_H #define TRANSLATE_H -#ifdef __GNUC__ -#pragma interface -#endif /* __GNUC__ */ - #include #include