spot/lbtt/src/UserCommandReader.cc
Alexandre Duret-Lutz ababb9ff93 Initial revision
2002-10-01 14:21:01 +00:00

927 lines
25 KiB
C++
Raw Blame History

/*
* Copyright (C) 1999, 2000, 2001, 2002
* Heikki Tauriainen <Heikki.Tauriainen@hut.fi>
*
* 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.
*/
#ifdef __GNUC__
#pragma implementation
#endif /* __GNUC__ */
#include <config.h>
#include <csignal>
#include <cstdio>
#include <iostream>
#include <fstream>
#ifdef HAVE_SSTREAM
#include <sstream>
#else
#include <strstream>
#endif /* HAVE_SSTREAM */
#include "DispUtil.h"
#include "ProductAutomaton.h"
#include "SharedTestData.h"
#include "StatDisplay.h"
#include "StringUtil.h"
#include "TestRoundInfo.h"
#include "TestStatistics.h"
#include "TestOperations.h"
#include "UserCommandReader.h"
#include "UserCommands.h"
#ifdef HAVE_READLINE
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <readline/readline.h>
#include <readline/history.h>
#endif /* HAVE_READLINE */
/******************************************************************************
*
* Functions for reading and parsing user commands.
*
*****************************************************************************/
namespace UserCommandInterface
{
using namespace ::SharedTestData;
using namespace ::StatDisplay;
using namespace ::StringUtil;
using namespace ::UserCommands;
/* ========================================================================= */
void executeUserCommands()
/* ----------------------------------------------------------------------------
*
* Description: Loop for reading user commands and executing them after a
* test round.
*
* Arguments: None.
*
* Returns: Nothing. However, changes `round_info.current_round',
* `round_info.next_round_to_stop',
* `round_info.next_round_to_run' and `round_info.abort' as a
* side effect, depending on the user's wish to abort testing,
* skip some number of test rounds or continue testing for a
* number of rounds.
*
* ------------------------------------------------------------------------ */
{
string input_line;
vector<string, ALLOC(string) > input_tokens;
TokenType token;
bool formula_type = true;
pair<string, bool> redirection_info;
string external_command;
ofstream* output_file = 0;
#ifdef HAVE_SSTREAM
ostringstream* output_string = 0;
#else
ostrstream* output_string = 0;
#endif /* HAVE_SSTREAM */
ostream* output_stream = 0;
const string prompt = " ** [Round " + toString(round_info.current_round)
+ " of " + toString(configuration.global_options.
number_of_rounds)
+ "] >> ";
int indent;
#ifdef HAVE_READLINE
char* prompt_c_str = new char[prompt.length() + 1];
strcpy(prompt_c_str, prompt.c_str());
char* line;
try
{
#endif /* HAVE_READLINE */
ProductAutomaton* product_automaton = 0;
pair<unsigned long int, bool> last_computed_product_automaton;
signal(SIGPIPE, SIG_IGN);
while (1)
{
try
{
input_line = "";
#ifdef HAVE_READLINE
line = readline(prompt_c_str);
if (line != static_cast<char*>(0))
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 */
external_command = "";
string::size_type pipe_pos = input_line.find_first_of('|');
if (pipe_pos != string::npos)
{
string::size_type nonspace_pos
= input_line.find_first_not_of(" \t", pipe_pos + 1);
if (nonspace_pos != string::npos)
{
external_command = input_line.substr(nonspace_pos);
input_line = input_line.substr(0, pipe_pos);
}
}
sliceString(input_line, " \t", input_tokens);
user_break = false;
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)
{
verifyArgumentCount(input_tokens, 0, 1);
unsigned long int rounds_to_continue;
rounds_to_continue = (input_tokens.size() > 1
? parseNumber(input_tokens[1])
: 1);
if (rounds_to_continue == 0)
throw CommandErrorException("Argument of the command must be "
"positive.");
if (token == CONTINUE)
{
bool all_algorithms_disabled = true;
for (vector<Configuration::AlgorithmInformation,
ALLOC(Configuration::AlgorithmInformation) >
::const_iterator
algorithm = configuration.algorithms.begin();
algorithm != configuration.algorithms.end();
++algorithm)
{
if (algorithm->enabled)
{
all_algorithms_disabled = false;
break;
}
}
/*
* Show a warning if the `continue' command would result in no
* further tests in the case that none of the implementations is
* enabled for testing.
*/
if (configuration.global_options.interactive
!= Configuration::ALWAYS
&& all_algorithms_disabled)
{
round_info.cout << " Warning! All algorithms are currently "
"disabled.\n Are you sure you wish to "
"continue? [y/n] ";
round_info.cout.flush();
input_line = "";
getline(cin, input_line, '\n');
sliceString(input_line, " \t", input_tokens);
if (!input_tokens.empty()
&& (input_tokens[0][0] == 'y' || input_tokens[0][0] == 'Y'))
round_info.next_round_to_stop
= configuration.global_options.number_of_rounds + 1;
else
{
round_info.cout << '\n';
round_info.cout.flush();
continue;
}
}
if (round_info.next_round_to_run == round_info.current_round)
round_info.next_round_to_run++;
if (configuration.global_options.interactive
== Configuration::ALWAYS
|| input_tokens.size() > 1)
round_info.next_round_to_stop
= round_info.current_round + rounds_to_continue;
}
else /* token == SKIP */
{
round_info.next_round_to_stop
= round_info.current_round + rounds_to_continue;
round_info.next_round_to_run = round_info.next_round_to_stop + 1;
}
break;
}
else if (token == ENABLE || token == DISABLE)
{
verifyArgumentCount(input_tokens, 0, 1);
changeAlgorithmState(input_tokens, token == ENABLE);
continue;
}
else if (token == QUIT)
{
verifyArgumentCount(input_tokens, 0, 0);
round_info.abort = true;
break;
}
else if (token == VERBOSITY)
{
verifyArgumentCount(input_tokens, 0, 1);
changeVerbosity(input_tokens);
continue;
}
if (round_info.skip
&& (token == BUCHI || token == BUCHIANALYZE
|| token == CONSISTENCYANALYSIS || token == EVALUATE
|| token == FORMULA || token == INCONSISTENCIES
|| token == RESULTANALYZE || token == STATESPACE))
throw CommandErrorException("This command is not available because "
"the current test round was skipped.");
/*
* If the command expects a formula identifier as a parameter,
* determine the type of the formula to which the command refers.
*/
if (token == BUCHI || token == EVALUATE
|| token == FORMULA || token == RESULTANALYZE)
formula_type = parseFormulaType(input_tokens);
if (!external_command.empty())
{
/*
* If the command output should be piped to an external program,
* prepare to collect the output into a string. In this case no
* output redirection (> or >>) is allowed.
*/
#ifdef HAVE_SSTREAM
output_string = new ostringstream();
#else
output_string = new ostrstream();
#endif /* HAVE_SSTREAM */
output_stream = output_string;
indent = 0;
}
else
{
/*
* Determine whether the output of the command should be saved or
* appended to a file, instead of displaying it on the console. If
* output redirection is required, open a file for output.
*/
redirection_info = parseRedirection(input_tokens);
if (redirection_info.first.empty())
{
output_stream = &cout;
indent = 2;
}
else
{
output_file = new ofstream();
try
{
TestOperations::openFile(redirection_info.first.c_str(),
*output_file,
ios::out | (redirection_info.second
? ios::app
: ios::trunc),
2);
}
catch (const IOException& e)
{
delete output_file;
output_file = 0;
throw CommandErrorException(e.what());
}
output_stream = output_file;
indent = 0;
}
}
switch (token)
{
case ALGORITHMS :
verifyArgumentCount(input_tokens, 0, 0);
printAlgorithmList(*output_stream, indent);
if (output_file != 0)
round_info.cout << " List of algorithms";
break;
case BUCHI :
{
bool use_dot = (input_tokens.size() == 3
&& input_tokens[2] == "dot");
verifyArgumentCount(input_tokens, 1, 2);
printBuchiAutomaton(*output_stream, indent,
formula_type,
input_tokens,
(use_dot ? Graph::DOT : Graph::NORMAL));
if (output_file != 0)
{
round_info.cout << " B<>chi automaton information";
if (use_dot)
round_info.cout << " (in dot format)";
}
}
break;
case BUCHIANALYZE :
verifyArgumentCount(input_tokens, 2, 2);
printAutomatonAnalysisResults(*output_stream, indent,
parseNumber(input_tokens[1]),
parseNumber(input_tokens[2]));
if (output_file != 0)
round_info.cout << " B<>chi automaton intersection emptiness "
"check analysis";
break;
case CONSISTENCYANALYSIS :
verifyArgumentCount(input_tokens, 1, 2);
printConsistencyAnalysisResults(*output_stream, indent,
input_tokens);
if (output_file != 0)
round_info.cout << " Consistency check result analysis";
break;
case EVALUATE :
verifyArgumentCount(input_tokens, 0, 2);
evaluateFormula(*output_stream, indent, formula_type,
input_tokens);
if (output_file != 0)
round_info.cout << " Formula acceptance information";
break;
case FORMULA :
verifyArgumentCount(input_tokens, 0, 0);
printFormula(*output_stream, indent, formula_type);
if (output_file != 0)
round_info.cout << string(" ") + (formula_type
? "Formula"
: "Negated formula");
break;
case HELP :
verifyArgumentCount(input_tokens, 0, 1);
printCommandHelp(*output_stream, indent, input_tokens);
if (output_file != 0)
round_info.cout << " Command help";
break;
case INCONSISTENCIES :
verifyArgumentCount(input_tokens, 0, 1);
printInconsistencies(*output_stream, indent, input_tokens);
if (output_file != 0)
round_info.cout << " Model checking result consistency check "
"results for round "
+ toString(round_info.current_round)
+ "\n ";
break;
case RESULTANALYZE :
verifyArgumentCount(input_tokens, 2, 3);
printCrossComparisonAnalysisResults
(*output_stream, indent, formula_type, input_tokens,
product_automaton, last_computed_product_automaton);
if (output_file != 0)
round_info.cout << " Model checking result cross-comparison "
"analysis";
break;
case RESULTS :
verifyArgumentCount(input_tokens, 0, 1);
printTestResults(*output_stream, indent, input_tokens);
if (output_file != 0)
round_info.cout << " Test results for round "
+ toString(round_info.current_round);
break;
case STATESPACE :
{
bool use_dot = (input_tokens.size() == 2
&& input_tokens[1] == "dot");
verifyArgumentCount(input_tokens, 0, 1);
printStateSpace(*output_stream, indent, input_tokens,
(use_dot ? Graph::DOT : Graph::NORMAL));
if (output_file != 0)
{
round_info.cout << " State space information";
if (use_dot)
round_info.cout << " (in dot format)";
}
}
break;
case STATISTICS :
verifyArgumentCount(input_tokens, 0, 0);
printCollectiveStats(*output_stream, indent);
if (output_file != 0)
round_info.cout << " Test statistics after round "
+ toString(round_info.current_round);
break;
default :
throw CommandErrorException("Unknown command (`"
+ input_tokens[0] + "').");
}
if (output_string != 0)
{
*output_stream << ends;
string outstring(output_string->str());
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);
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)
{
round_info.cout << string(redirection_info.second
? " appended"
: " written")
+ " to `" + redirection_info.first + "'.\n\n";
round_info.cout.flush();
}
}
else if (!external_command.empty())
{
system(external_command.c_str());
round_info.cout << '\n';
round_info.cout.flush();
}
}
catch (const Exception& e)
{
::DispUtil::printTextBlock(cout, 2, string("Error: ") + e.what() + '\n',
78);
}
if (output_string != 0)
{
#ifndef HAVE_SSTREAM
output_string->freeze(0);
#endif /* HAVE_SSTREAM */
delete output_string;
output_string = 0;
}
else if (output_file != 0)
{
output_file->close();
delete output_file;
output_file = 0;
}
}
if (product_automaton != 0)
{
::DispUtil::printText
("<cleaning up memory allocated for product automaton>", 4, 2);
delete product_automaton;
::DispUtil::printText(" ok\n", 4);
}
#ifdef HAVE_READLINE
}
catch (...)
{
delete[] prompt_c_str;
throw;
}
delete[] prompt_c_str;
#endif /* HAVE_READLINE */
signal(SIGPIPE, SIG_DFL);
}
/* ========================================================================= */
TokenType parseCommand(const string& token)
/* ----------------------------------------------------------------------------
*
* Description: Parses a user command by translating a command name into
* its corresponding TokenType identifier.
*
* Argument: token -- A reference to a string containing the command.
*
* Returns: A command identifier of the enumerated type TokenType.
*
* ------------------------------------------------------------------------- */
{
/*
* 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;
if (token.empty())
return token_type;
switch (token[0])
{
case 'a' :
if (token.compare(1, len - 1, "lgorithms", len - 1) == 0)
token_type = ALGORITHMS;
break;
case 'b' :
if (len < 2)
ambiguous = true;
else if (token[1] == 'u')
{
if (len < 3)
ambiguous = true;
else if (token[2] == 'c')
{
if (len < 4)
ambiguous = true;
else if (token[3] == 'h')
{
if (len < 5)
ambiguous = true;
else if (token[4] == 'i')
{
if (len < 6)
token_type = BUCHI;
else if (token[5] == 'a'
&& token.compare(6, len - 6, "nalysis", len - 6) == 0)
token_type = BUCHIANALYZE;
}
}
}
}
break;
case 'c' :
if (len < 2)
ambiguous = true;
else if (token[1] == 'o')
{
if (len < 3)
ambiguous = true;
else if (token[2] == 'n')
{
if (len < 4)
ambiguous = true;
else if (token[3] == 's'
&& token.compare(4, len - 4, "istencyanalysis", len - 4)
== 0)
token_type = CONSISTENCYANALYSIS;
else if (token[3] == 't'
&& token.compare(4, len - 4, "inue", len - 4) == 0)
token_type = CONTINUE;
}
}
break;
case 'd' :
if (token.compare(1, len - 1, "isable", len - 1) == 0)
token_type = DISABLE;
break;
case 'e' :
if (len < 2)
ambiguous = true;
else if (token[1] == 'n')
{
if (token.compare(2, len - 2, "able", len - 2) == 0)
token_type = ENABLE;
}
else if (token[1] == 'v')
{
if (token.compare(2, len - 2, "aluate", len - 2) == 0)
token_type = EVALUATE;
}
break;
case 'f' :
if (token.compare(1, len - 1, "ormula", len - 1) == 0)
token_type = FORMULA;
break;
case 'h' :
if (token.compare(1, len - 1, "elp", len - 1) == 0)
token_type = HELP;
break;
case 'i' :
if (token.compare(1, len - 1, "nconsistencies", len - 1) == 0)
token_type = INCONSISTENCIES;
break;
case 'q' :
if (token.compare(1, len - 1, "uit", len - 1) == 0)
token_type = QUIT;
break;
case 'r' :
if (len < 2)
ambiguous = true;
else if (token[1] == 'e')
{
if (len < 3)
ambiguous = true;
else if (token[2] == 's')
{
if (len < 4)
ambiguous = true;
else if (token[3] == 'u')
{
if (len < 5)
ambiguous = true;
else if (token[4] == 'l')
{
if (len < 6)
ambiguous = true;
else if (token[5] == 't')
{
if (len < 7)
ambiguous = true;
else if (token[6] == 's' && len == 7)
token_type = RESULTS;
else if (token[6] == 'a')
{
if (token.compare(7, len - 7, "nalysis", len - 7) == 0)
token_type = RESULTANALYZE;
}
}
}
}
}
}
break;
case 's' :
if (len < 2)
ambiguous = true;
else if (token[1] == 'k')
{
if (token.compare(2, len - 2, "ip", len - 2) == 0)
token_type = SKIP;
}
else if (token[1] == 't')
{
if (len < 3)
ambiguous = true;
else if (token[2] == 'a')
{
if (len < 4)
ambiguous = true;
else if (token[3] == 't')
{
if (len < 5)
ambiguous = true;
else if (token[4] == 'e')
{
if (token.compare(5, len - 5, "space", len - 5) == 0)
token_type = STATESPACE;
}
else if (token[4] == 'i')
{
if (token.compare(5, len - 5, "stics", len - 5) == 0)
token_type = STATISTICS;
}
}
}
}
break;
case 'v' :
if (token.compare(1, len - 1, "erbosity", len - 1) == 0)
token_type = VERBOSITY;
break;
}
if (ambiguous)
throw CommandErrorException("Ambiguous command.");
return token_type;
#ifdef __GNUC__
#if __GNUC__ < 3
#undef compare
#endif
#endif
}
/* ========================================================================= */
void verifyArgumentCount
(const vector<string, ALLOC(string) >& command,
vector<string, ALLOC(string) >::size_type min_arg_count,
vector<string, ALLOC(string) >::size_type max_arg_count)
/* ----------------------------------------------------------------------------
*
* Description: Verifies that the number of arguments given for a user
* command is between a given interval.
*
* Arguments: command -- A reference to a constant vector of
* strings (the user command and its
* arguments).
* min_arg_count -- Smallest allowed number of arguments.
* max_arg_count -- Largest allowed number of arguments.
*
* Returns: Nothing.
*
* ------------------------------------------------------------------------- */
{
if (command.size() < min_arg_count + 1)
throw CommandErrorException("Too few arguments for command.");
else if (command.size() > max_arg_count + 1)
throw CommandErrorException("Too many arguments for command.");
}
/* ========================================================================= */
pair<string, bool> parseRedirection
(vector<string, ALLOC(string) >& input_tokens)
/* ----------------------------------------------------------------------------
*
* Description: Tests whether the last argument to a user command specifies
* output redirection. If redirection is requested, the
* "argument" specifying the redirection is removed from the
* vector of strings forming the command.
*
* Argument: input_tokens -- A reference to a vector of strings giving
* the user command and its arguments.
*
* Returns: A pair whose first component is the name of the output file
* (or the empty string if no redirection was specified) and
* whose second component determines whether the output should
* be appended to the file instead of creating a new file.
*
* ------------------------------------------------------------------------- */
{
string filename;
bool append = false;
if (!input_tokens.empty())
{
string& token = input_tokens.back();
if (token[0] == '>')
{
if (token.length() > 1)
{
if (token[1] == '>')
{
if (token.length() > 2)
{
append = true;
filename = token.substr(2);
input_tokens.pop_back();
}
}
else
{
filename = token.substr(1);
input_tokens.pop_back();
}
}
}
else if (input_tokens.size() >= 2)
{
string& token = *(input_tokens.rbegin() + 1);
if (token[0] == '>' && (token.length() == 1
|| (token.length() == 2 && token[1] == '>')))
{
filename = input_tokens.back();
append = (token.length() == 2);
input_tokens.pop_back();
input_tokens.pop_back();
}
}
}
return make_pair(filename, append);
}
/* ========================================================================= */
bool parseFormulaType(vector<string, ALLOC(string) >& input_tokens)
/* ----------------------------------------------------------------------------
*
* Description: Tests whether the first argument of a command specifies a
* formula (i.e., whether the first argument of the command is
* either a `+' or a `-'). If it is, the argument is removed
* from the vector of strings forming the command.
*
* Argument: input_tokens -- A reference to a vector of strings giving
* the user command.
*
* Returns: A truth value according to whether a formula or its negation
* was specified; the effect of specifying no formula type is
* the same as giving a `+' as an argument (i.e. the formula
* type defaults to the positive formula).
*
* ------------------------------------------------------------------------- */
{
bool formula_type = true;
if (input_tokens.size() >= 2)
{
formula_type = (input_tokens[1] != "-");
if (input_tokens[1].length() == 1
&& (input_tokens[1][0] == '+' || input_tokens[1][0] == '-'))
input_tokens.erase(input_tokens.begin());
}
return formula_type;
}
/* ========================================================================= */
void verifyNumber
(unsigned long int number, unsigned long int max, const char* error_message)
/* ----------------------------------------------------------------------------
*
* Description: Checks that a given unsigned long integer is less than a
* given maximum value. Throws an exception with an error
* message if this is not the case.
*
* Argument: number -- Number to be tested.
* max -- Value the number is to be tested against.
* error_message -- Error message.
*
* Returns: Nothing. Throws an exception if the check fails.
*
* ------------------------------------------------------------------------- */
{
if (number >= max)
throw CommandErrorException(string(error_message) + " ("
+ toString(number) + ").");
}
}