From 180cf1538ad2b19bd6b4108662adc4a519565fcb Mon Sep 17 00:00:00 2001 From: Teemu Murtola Date: Mon, 29 Jun 2015 07:58:04 +0300 Subject: [PATCH 1/1] Refactor for testing interactive selection input Introduce a parseInteractive() method that works otherwise as parseFromStdin(), except that the caller can provide alternative streams for input and status output. Make all input and output use these streams instead of stdin or stderr directly. Unit tests using this will be added separately. There is slight regression in the formatting of errors during interactive selection input (noted in TODO in exceptions.cpp), but that will be resolved separately to keep the commits smaller. Change-Id: I0b6b885621c81a5e526b5f93b40d32b9248626f1 --- src/gromacs/selection/indexutil.cpp | 31 ++-- src/gromacs/selection/indexutil.h | 11 +- src/gromacs/selection/parsetree.cpp | 28 ++-- src/gromacs/selection/scanner.cpp | 4 +- src/gromacs/selection/scanner.h | 13 +- src/gromacs/selection/scanner.l | 2 +- src/gromacs/selection/scanner_internal.cpp | 12 +- src/gromacs/selection/scanner_internal.h | 5 +- src/gromacs/selection/selection.cpp | 4 +- src/gromacs/selection/selectioncollection.cpp | 142 ++++++++++-------- src/gromacs/selection/selectioncollection.h | 31 +++- src/gromacs/utility/exceptions.cpp | 44 +++++- src/gromacs/utility/exceptions.h | 11 ++ src/gromacs/utility/textwriter.cpp | 5 + src/gromacs/utility/textwriter.h | 17 +++ 15 files changed, 254 insertions(+), 106 deletions(-) diff --git a/src/gromacs/selection/indexutil.cpp b/src/gromacs/selection/indexutil.cpp index 2e85724ac4..8aecd065e7 100644 --- a/src/gromacs/selection/indexutil.cpp +++ b/src/gromacs/selection/indexutil.cpp @@ -56,6 +56,8 @@ #include "gromacs/utility/exceptions.h" #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/smalloc.h" +#include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" /******************************************************************** * gmx_ana_indexgrps_t functions @@ -261,18 +263,19 @@ gmx_ana_indexgrps_find(gmx_ana_index_t *dest, std::string *destName, } /*! - * \param[in] fp Where to print the output. + * \param[in] writer Writer to use for output. * \param[in] g Index groups to print. * \param[in] maxn Maximum number of indices to print * (-1 = print all, 0 = print only names). */ void -gmx_ana_indexgrps_print(FILE *fp, gmx_ana_indexgrps_t *g, int maxn) +gmx_ana_indexgrps_print(gmx::TextWriter *writer, gmx_ana_indexgrps_t *g, int maxn) { for (int i = 0; i < g->nr; ++i) { - fprintf(fp, " Group %2d \"%s\" ", i, g->names[i].c_str()); - gmx_ana_index_dump(fp, &g->g[i], maxn); + writer->writeString(gmx::formatString(" Group %2d \"%s\" ", + i, g->names[i].c_str())); + gmx_ana_index_dump(writer, &g->g[i], maxn); } } @@ -394,34 +397,32 @@ gmx_ana_index_copy(gmx_ana_index_t *dest, gmx_ana_index_t *src, bool bAlloc) } /*! - * \param[in] fp Where to print the output. + * \param[in] writer Writer to use for output. * \param[in] g Index group to print. * \param[in] maxn Maximum number of indices to print (-1 = print all). */ void -gmx_ana_index_dump(FILE *fp, gmx_ana_index_t *g, int maxn) +gmx_ana_index_dump(gmx::TextWriter *writer, gmx_ana_index_t *g, int maxn) { - int j, n; - - fprintf(fp, "(%d atoms)", g->isize); + writer->writeString(gmx::formatString("(%d atoms)", g->isize)); if (maxn != 0) { - fprintf(fp, ":"); - n = g->isize; + writer->writeString(":"); + int n = g->isize; if (maxn >= 0 && n > maxn) { n = maxn; } - for (j = 0; j < n; ++j) + for (int j = 0; j < n; ++j) { - fprintf(fp, " %d", g->index[j]+1); + writer->writeString(gmx::formatString(" %d", g->index[j]+1)); } if (n < g->isize) { - fprintf(fp, " ..."); + writer->writeString(" ..."); } } - fprintf(fp, "\n"); + writer->writeLine(); } int diff --git a/src/gromacs/selection/indexutil.h b/src/gromacs/selection/indexutil.h index 7ec5bd9691..9ac9364008 100644 --- a/src/gromacs/selection/indexutil.h +++ b/src/gromacs/selection/indexutil.h @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2009,2010,2011,2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2009,2010,2011,2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -68,6 +68,11 @@ #include "gromacs/legacyheaders/types/simple.h" #include "gromacs/topology/block.h" +namespace gmx +{ +class TextWriter; +} + struct t_topology; /** Stores a set of index groups. */ @@ -205,7 +210,7 @@ gmx_ana_indexgrps_find(gmx_ana_index_t *dest, std::string *destName, /** Writes out a list of index groups. */ void -gmx_ana_indexgrps_print(FILE *fp, gmx_ana_indexgrps_t *g, int maxn); +gmx_ana_indexgrps_print(gmx::TextWriter *writer, gmx_ana_indexgrps_t *g, int maxn); /*@}*/ /*! \name Functions for handling gmx_ana_index_t @@ -235,7 +240,7 @@ gmx_ana_index_copy(gmx_ana_index_t *dest, gmx_ana_index_t *src, bool bAlloc); /** Writes out the contents of a index group. */ void -gmx_ana_index_dump(FILE *fp, gmx_ana_index_t *g, int maxn); +gmx_ana_index_dump(gmx::TextWriter *writer, gmx_ana_index_t *g, int maxn); /*! \brief * Returns maximum atom index that appears in an index group. diff --git a/src/gromacs/selection/parsetree.cpp b/src/gromacs/selection/parsetree.cpp index 79162acd92..d1eb39fcf3 100644 --- a/src/gromacs/selection/parsetree.cpp +++ b/src/gromacs/selection/parsetree.cpp @@ -66,8 +66,9 @@ * methods and initializes the children of the method element. * - selectioncollection.h, selectioncollection.cpp: * These files define the high-level public interface to the parser - * through SelectionCollection::parseFromStdin(), - * SelectionCollection::parseFromFile() and + * through SelectionCollection::parseInteractive(), + * SelectionCollection::parseFromStdin(), + * SelectionCollection::parseFromFile(), and * SelectionCollection::parseFromString(). * * The basic control flow in the parser is as follows: when a parser function @@ -234,6 +235,7 @@ #include "gromacs/utility/exceptions.h" #include "gromacs/utility/smalloc.h" #include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" #include "keywords.h" #include "poscalc.h" @@ -309,9 +311,11 @@ _gmx_selparser_handle_error(yyscan_t scanner) catch (gmx::UserInputError &ex) { ex.prependContext(context); - if (_gmx_sel_is_lexer_interactive(scanner)) + gmx::TextWriter *statusWriter + = _gmx_sel_lexer_get_status_writer(scanner); + if (statusWriter != NULL) { - gmx::formatExceptionMessageToFile(stderr, ex); + gmx::formatExceptionMessageToWriter(statusWriter, ex); return true; } throw; @@ -1061,10 +1065,13 @@ _gmx_sel_init_selection(const char *name, root->fillNameIfMissing(_gmx_sel_lexer_pselstr(scanner)); /* Print out some information if the parser is interactive */ - if (_gmx_sel_is_lexer_interactive(scanner)) + gmx::TextWriter *statusWriter = _gmx_sel_lexer_get_status_writer(scanner); + if (statusWriter != NULL) { - fprintf(stderr, "Selection '%s' parsed\n", - _gmx_sel_lexer_pselstr(scanner)); + const std::string message + = gmx::formatString("Selection '%s' parsed", + _gmx_sel_lexer_pselstr(scanner)); + statusWriter->writeLine(message); } return root; @@ -1128,9 +1135,12 @@ _gmx_sel_assign_variable(const char *name, srenew(sc->varstrs, sc->nvars + 1); sc->varstrs[sc->nvars] = gmx_strdup(pselstr); ++sc->nvars; - if (_gmx_sel_is_lexer_interactive(scanner)) + gmx::TextWriter *statusWriter = _gmx_sel_lexer_get_status_writer(scanner); + if (statusWriter != NULL) { - fprintf(stderr, "Variable '%s' parsed\n", pselstr); + const std::string message + = gmx::formatString("Variable '%s' parsed", pselstr); + statusWriter->writeLine(message); } return root; } diff --git a/src/gromacs/selection/scanner.cpp b/src/gromacs/selection/scanner.cpp index a37f3e8cf7..3cdfb93e73 100644 --- a/src/gromacs/selection/scanner.cpp +++ b/src/gromacs/selection/scanner.cpp @@ -495,7 +495,7 @@ static yyconst flex_int16_t yy_chk[151] = /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2009,2010,2011,2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2009,2010,2011,2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -932,7 +932,7 @@ case 6: YY_RULE_SETUP #line 137 "scanner.l" { - if (yytext[0] == ';' || state->bInteractive) + if (yytext[0] == ';' || state->statusWriter != NULL) { rtrim(state->pselstr); state->bCmdStart = true; diff --git a/src/gromacs/selection/scanner.h b/src/gromacs/selection/scanner.h index ad7fcf56ce..b918ccc5fd 100644 --- a/src/gromacs/selection/scanner.h +++ b/src/gromacs/selection/scanner.h @@ -51,6 +51,11 @@ #include "parser.h" +namespace gmx +{ +class TextWriter; +} + struct gmx_ana_indexgrps_t; struct gmx_ana_selcollection_t; @@ -62,7 +67,7 @@ typedef void *yyscan_t; /** Initializes the selection scanner. */ void _gmx_sel_init_lexer(yyscan_t *scannerp, struct gmx_ana_selcollection_t *sc, - bool bInteractive, int maxnr, bool bGroups, + gmx::TextWriter *statusWriter, int maxnr, bool bGroups, struct gmx_ana_indexgrps_t *grps); /** Frees memory allocated for the selection scanner. */ void @@ -77,9 +82,9 @@ _gmx_sel_lexer_set_exception(yyscan_t scanner, void _gmx_sel_lexer_rethrow_exception_if_occurred(yyscan_t scanner); -/** Returns true if the scanner is interactive. */ -bool -_gmx_sel_is_lexer_interactive(yyscan_t scanner); +/** Returns writer for status output (if not NULL, the scanner is interactive). */ +gmx::TextWriter * +_gmx_sel_lexer_get_status_writer(yyscan_t scanner); /** Returns the selection collection for the scanner. */ struct gmx_ana_selcollection_t * _gmx_sel_lexer_selcollection(yyscan_t scanner); diff --git a/src/gromacs/selection/scanner.l b/src/gromacs/selection/scanner.l index f41234b2de..d2d2fd97ec 100644 --- a/src/gromacs/selection/scanner.l +++ b/src/gromacs/selection/scanner.l @@ -135,7 +135,7 @@ COMMENT (#.*) \\\n { _gmx_sel_lexer_add_token(yylloc, " ", 1, state); break; } ";"|\n { - if (yytext[0] == ';' || state->bInteractive) + if (yytext[0] == ';' || state->statusWriter != NULL) { rtrim(state->pselstr); state->bCmdStart = true; diff --git a/src/gromacs/selection/scanner_internal.cpp b/src/gromacs/selection/scanner_internal.cpp index 1f3be505e5..d66d647b58 100644 --- a/src/gromacs/selection/scanner_internal.cpp +++ b/src/gromacs/selection/scanner_internal.cpp @@ -384,8 +384,8 @@ _gmx_sel_lexer_add_token(YYLTYPE *yylloc, const char *str, int len, void _gmx_sel_init_lexer(yyscan_t *scannerp, struct gmx_ana_selcollection_t *sc, - bool bInteractive, int maxnr, bool bGroups, - struct gmx_ana_indexgrps_t *grps) + gmx::TextWriter *statusWriter, int maxnr, + bool bGroups, struct gmx_ana_indexgrps_t *grps) { int rc = _gmx_sel_yylex_init(scannerp); if (rc != 0) @@ -401,7 +401,7 @@ _gmx_sel_init_lexer(yyscan_t *scannerp, struct gmx_ana_selcollection_t *sc, state->grps = grps; state->nexpsel = (maxnr > 0 ? static_cast(sc->sel.size()) + maxnr : -1); - state->bInteractive = bInteractive; + state->statusWriter = statusWriter; snew(state->pselstr, STRSTORE_ALLOCSTEP); state->pselstr[0] = 0; @@ -461,11 +461,11 @@ _gmx_sel_lexer_rethrow_exception_if_occurred(yyscan_t scanner) } } -bool -_gmx_sel_is_lexer_interactive(yyscan_t scanner) +gmx::TextWriter * +_gmx_sel_lexer_get_status_writer(yyscan_t scanner) { gmx_sel_lexer_t *state = _gmx_sel_yyget_extra(scanner); - return state->bInteractive; + return state->statusWriter; } struct gmx_ana_selcollection_t * diff --git a/src/gromacs/selection/scanner_internal.h b/src/gromacs/selection/scanner_internal.h index 5bd8eb0e43..7b01b81e27 100644 --- a/src/gromacs/selection/scanner_internal.h +++ b/src/gromacs/selection/scanner_internal.h @@ -48,6 +48,7 @@ namespace gmx { class SelectionParserSymbol; +class TextWriter; } /* These need to be defined before including scanner_flex.h, because it @@ -81,8 +82,8 @@ typedef struct gmx_sel_lexer_t //! Number of selections at which the parser should stop. int nexpsel; - //! Whether the parser is interactive. - bool bInteractive; + //! Writer to use for status output (if not NULL, parser is interactive). + gmx::TextWriter *statusWriter; //! Pretty-printed version of the string parsed since last clear. char *pselstr; diff --git a/src/gromacs/selection/selection.cpp b/src/gromacs/selection/selection.cpp index 7b77d5c5ac..8c81ca51ac 100644 --- a/src/gromacs/selection/selection.cpp +++ b/src/gromacs/selection/selection.cpp @@ -51,6 +51,7 @@ #include "gromacs/utility/exceptions.h" #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" #include "selelem.h" #include "selvalue.h" @@ -320,7 +321,8 @@ Selection::printDebugInfo(FILE *fp, int nmaxind) const fprintf(fp, " Group "); gmx_ana_index_t g; gmx_ana_index_set(&g, p.m.mapb.nra, p.m.mapb.a, 0); - gmx_ana_index_dump(fp, &g, nmaxind); + TextWriter writer(fp); + gmx_ana_index_dump(&writer, &g, nmaxind); fprintf(fp, " Block (size=%d):", p.m.mapb.nr); if (!p.m.mapb.index) diff --git a/src/gromacs/selection/selectioncollection.cpp b/src/gromacs/selection/selectioncollection.cpp index aba20c7717..bbe3f11df2 100644 --- a/src/gromacs/selection/selectioncollection.cpp +++ b/src/gromacs/selection/selectioncollection.cpp @@ -49,6 +49,7 @@ #include #include +#include #include #include "gromacs/fileio/trx.h" @@ -65,6 +66,7 @@ #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/smalloc.h" #include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" #include "compiler.h" #include "mempool.h" @@ -129,43 +131,44 @@ namespace /*! \brief * Reads a single selection line from stdin. * - * \param[in] infile Stream to read from (typically the StandardInputStream). - * \param[in] bInteractive Whether to print interactive prompts. + * \param[in] inputStream Stream to read from (typically the StandardInputStream). + * \param[in] statusWriter Stream to print prompts to (if NULL, no output is done). * \param[out] line The read line in stored here. * \returns true if something was read, false if at end of input. * * Handles line continuation, reading also the continuing line(s) in one call. */ -bool promptLine(TextInputStream *infile, bool bInteractive, std::string *line) +bool promptLine(TextInputStream *inputStream, TextWriter *statusWriter, + std::string *line) { - if (bInteractive) + if (statusWriter != NULL) { - fprintf(stderr, "> "); + statusWriter->writeString("> "); } - if (!infile->readLine(line)) + if (!inputStream->readLine(line)) { return false; } while (endsWith(*line, "\\\n")) { line->resize(line->length() - 2); - if (bInteractive) + if (statusWriter != NULL) { - fprintf(stderr, "... "); + statusWriter->writeString("... "); } std::string buffer; // Return value ignored, buffer remains empty and works correctly // if there is nothing to read. - infile->readLine(&buffer); + inputStream->readLine(&buffer); line->append(buffer); } if (endsWith(*line, "\n")) { line->resize(line->length() - 1); } - else if (bInteractive) + else if (statusWriter != NULL) { - fprintf(stderr, "\n"); + statusWriter->writeLine(); } return true; } @@ -216,6 +219,7 @@ int runParserLoop(yyscan_t scanner, _gmx_sel_yypstate *parserState, /*! \brief * Print current status in response to empty line in interactive input. * + * \param[in] writer Writer to use for the output. * \param[in] sc Selection collection data structure. * \param[in] grps Available index groups. * \param[in] firstSelection Index of first selection from this interactive @@ -227,52 +231,54 @@ int runParserLoop(yyscan_t scanner, _gmx_sel_yypstate *parserState, * * Prints the available index groups and currently provided selections. */ -void printCurrentStatus(gmx_ana_selcollection_t *sc, gmx_ana_indexgrps_t *grps, - size_t firstSelection, int maxCount, - const std::string &context, bool bFirst) +void printCurrentStatus(TextWriter *writer, gmx_ana_selcollection_t *sc, + gmx_ana_indexgrps_t *grps, size_t firstSelection, + int maxCount, const std::string &context, bool bFirst) { if (grps != NULL) { - std::fprintf(stderr, "Available static index groups:\n"); - gmx_ana_indexgrps_print(stderr, grps, 0); + writer->writeLine("Available static index groups:"); + gmx_ana_indexgrps_print(writer, grps, 0); } - std::fprintf(stderr, "Specify "); + writer->writeString("Specify "); if (maxCount < 0) { - std::fprintf(stderr, "any number of selections"); + writer->writeString("any number of selections"); } else if (maxCount == 1) { - std::fprintf(stderr, "a selection"); + writer->writeString("a selection"); } else { - std::fprintf(stderr, "%d selections", maxCount); + writer->writeString(formatString("%d selections", maxCount)); } - std::fprintf(stderr, "%s%s:\n", - context.empty() ? "" : " ", context.c_str()); - std::fprintf(stderr, - "(one per line, for status/groups, 'help' for help%s)\n", - maxCount < 0 ? ", Ctrl-D to end" : ""); + writer->writeString(formatString("%s%s:\n", + context.empty() ? "" : " ", context.c_str())); + writer->writeString(formatString( + "(one per line, for status/groups, 'help' for help%s)\n", + maxCount < 0 ? ", Ctrl-D to end" : "")); if (!bFirst && (sc->nvars > 0 || sc->sel.size() > firstSelection)) { - std::fprintf(stderr, "Currently provided selections:\n"); + writer->writeLine("Currently provided selections:"); for (int i = 0; i < sc->nvars; ++i) { - std::fprintf(stderr, " %s\n", sc->varstrs[i]); + writer->writeString(formatString(" %s\n", sc->varstrs[i])); } for (size_t i = firstSelection; i < sc->sel.size(); ++i) { - std::fprintf(stderr, " %2d. %s\n", - static_cast(i - firstSelection + 1), - sc->sel[i]->selectionText()); + writer->writeString(formatString( + " %2d. %s\n", + static_cast(i - firstSelection + 1), + sc->sel[i]->selectionText())); } if (maxCount > 0) { const int remaining = maxCount - static_cast(sc->sel.size() - firstSelection); - std::fprintf(stderr, "(%d more selection%s required)\n", - remaining, remaining > 1 ? "s" : ""); + writer->writeString(formatString( + "(%d more selection%s required)\n", + remaining, remaining > 1 ? "s" : "")); } } } @@ -280,20 +286,21 @@ void printCurrentStatus(gmx_ana_selcollection_t *sc, gmx_ana_indexgrps_t *grps, /*! \brief * Prints selection help in interactive selection input. * + * \param[in] writer Writer to use for the output. * \param[in] sc Selection collection data structure. * \param[in] line Line of user input requesting help (starting with `help`). * * Initializes the selection help if not yet initialized, and finds the help * topic based on words on the input line. */ -void printHelp(gmx_ana_selcollection_t *sc, const std::string &line) +void printHelp(TextWriter *writer, gmx_ana_selcollection_t *sc, + const std::string &line) { if (sc->rootHelp.get() == NULL) { sc->rootHelp = createSelectionHelpTopic(); } - HelpWriterContext context(&TextOutputFile::standardError(), - eHelpOutputFormat_Console); + HelpWriterContext context(&writer->stream(), eHelpOutputFormat_Console); HelpManager manager(*sc->rootHelp, context); try { @@ -307,7 +314,7 @@ void printHelp(gmx_ana_selcollection_t *sc, const std::string &line) } catch (const InvalidInputError &ex) { - fprintf(stderr, "%s\n", ex.what()); + writer->writeLine(ex.what()); return; } manager.writeCurrentTopic(); @@ -317,8 +324,10 @@ void printHelp(gmx_ana_selcollection_t *sc, const std::string &line) * Helper function that runs the parser once the tokenizer has been * initialized. * - * \param[in,out] scanner Scanner data structure. - * \param[in] bStdIn Whether to use a line-based reading + * \param[in,out] scanner Scanner data structure. + * \param[in] inputStream Stream to use for input (currently only with + * `bInteractive==true`). + * \param[in] bInteractive Whether to use a line-based reading * algorithm designed for interactive input. * \param[in] maxnr Maximum number of selections to parse * (if -1, parse as many as provided by the user). @@ -327,11 +336,11 @@ void printHelp(gmx_ana_selcollection_t *sc, const std::string &line) * \throws std::bad_alloc if out of memory. * \throws InvalidInputError if there is a parsing error. * - * Used internally to implement parseFromStdin(), parseFromFile() and + * Used internally to implement parseInteractive(), parseFromFile() and * parseFromString(). */ -SelectionList runParser(yyscan_t scanner, bool bStdIn, int maxnr, - const std::string &context) +SelectionList runParser(yyscan_t scanner, TextInputStream *inputStream, + bool bInteractive, int maxnr, const std::string &context) { boost::shared_ptr scannerGuard(scanner, &_gmx_sel_free_lexer); gmx_ana_selcollection_t *sc = _gmx_sel_lexer_selcollection(scanner); @@ -341,30 +350,29 @@ SelectionList runParser(yyscan_t scanner, bool bStdIn, int maxnr, { boost::shared_ptr<_gmx_sel_yypstate> parserState( _gmx_sel_yypstate_new(), &_gmx_sel_yypstate_delete); - if (bStdIn) + if (bInteractive) { - TextInputStream &stdinFile(StandardInputStream::instance()); - const bool bInteractive = _gmx_sel_is_lexer_interactive(scanner); - if (bInteractive) + TextWriter *statusWriter = _gmx_sel_lexer_get_status_writer(scanner); + if (statusWriter != NULL) { - printCurrentStatus(sc, grps, oldCount, maxnr, context, true); + printCurrentStatus(statusWriter, sc, grps, oldCount, maxnr, context, true); } std::string line; int status; - while (promptLine(&stdinFile, bInteractive, &line)) + while (promptLine(inputStream, statusWriter, &line)) { - if (bInteractive) + if (statusWriter != NULL) { line = stripString(line); if (line.empty()) { - printCurrentStatus(sc, grps, oldCount, maxnr, context, false); + printCurrentStatus(statusWriter, sc, grps, oldCount, maxnr, context, false); continue; } if (startsWith(line, "help") && (line[4] == 0 || std::isspace(line[4]))) { - printHelp(sc, line); + printHelp(statusWriter, sc, line); continue; } } @@ -673,17 +681,31 @@ SelectionCollection::requiresTopology() const return false; } - SelectionList -SelectionCollection::parseFromStdin(int nr, bool bInteractive, +SelectionCollection::parseFromStdin(int count, bool bInteractive, const std::string &context) +{ + return parseInteractive(count, &StandardInputStream::instance(), + bInteractive ? &TextOutputFile::standardError() : NULL, + context); +} + +SelectionList +SelectionCollection::parseInteractive(int count, + TextInputStream *inputStream, + TextOutputStream *statusStream, + const std::string &context) { yyscan_t scanner; - _gmx_sel_init_lexer(&scanner, &impl_->sc_, bInteractive, nr, - impl_->bExternalGroupsSet_, - impl_->grps_); - return runParser(scanner, true, nr, context); + boost::scoped_ptr statusWriter; + if (statusStream != NULL) + { + statusWriter.reset(new TextWriter(statusStream)); + } + _gmx_sel_init_lexer(&scanner, &impl_->sc_, statusWriter.get(), + count, impl_->bExternalGroupsSet_, impl_->grps_); + return runParser(scanner, inputStream, true, count, context); } @@ -696,11 +718,11 @@ SelectionCollection::parseFromFile(const std::string &filename) yyscan_t scanner; TextInputFile file(filename); // TODO: Exception-safe way of using the lexer. - _gmx_sel_init_lexer(&scanner, &impl_->sc_, false, -1, + _gmx_sel_init_lexer(&scanner, &impl_->sc_, NULL, -1, impl_->bExternalGroupsSet_, impl_->grps_); _gmx_sel_set_lex_input_file(scanner, file.handle()); - return runParser(scanner, false, -1, std::string()); + return runParser(scanner, NULL, false, -1, std::string()); } catch (GromacsException &ex) { @@ -717,11 +739,11 @@ SelectionCollection::parseFromString(const std::string &str) { yyscan_t scanner; - _gmx_sel_init_lexer(&scanner, &impl_->sc_, false, -1, + _gmx_sel_init_lexer(&scanner, &impl_->sc_, NULL, -1, impl_->bExternalGroupsSet_, impl_->grps_); _gmx_sel_set_lex_input_str(scanner, str.c_str()); - return runParser(scanner, false, -1, std::string()); + return runParser(scanner, NULL, false, -1, std::string()); } diff --git a/src/gromacs/selection/selectioncollection.h b/src/gromacs/selection/selectioncollection.h index 8ec7ed1f23..826f20a04d 100644 --- a/src/gromacs/selection/selectioncollection.h +++ b/src/gromacs/selection/selectioncollection.h @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2010,2011,2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2010,2011,2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -63,6 +63,8 @@ namespace gmx class Options; class SelectionCompiler; class SelectionEvaluator; +class TextInputStream; +class TextOutputStream; /*! \brief * Collection of selections. @@ -79,7 +81,7 @@ class SelectionEvaluator; * initialization options. * * After setting the default values, one or more selections can be parsed with - * one or more calls to parseFromStdin(), parseFromFile(), and/or + * one or more calls to parseInteractive(), parseFromStdin(), parseFromFile(), and/or * parseFromString(). After all selections are parsed, the topology must be * set with setTopology() unless requiresTopology() returns false (the topology * can also be set earlier). @@ -257,6 +259,31 @@ class SelectionCollection */ SelectionList parseFromStdin(int count, bool bInteractive, const std::string &context); + /*! \brief + * Parses selection(s) interactively using provided streams. + * + * \param[in] count Number of selections to parse + * (if -1, parse as many as provided by the user). + * \param[in] inputStream Stream to use for input. + * \param[in] outputStream Stream to use for output + * (if NULL, the parser runs non-interactively and does not + * produce any status messages). + * \param[in] context Context to print for interactive input. + * \returns Vector of parsed selections. + * \throws std::bad_alloc if out of memory. + * \throws InvalidInputError if there is a parsing error + * (an interactive parser only throws this if too few selections + * are provided and the user forced the end of input). + * + * Works the same as parseFromStdin(), except that the caller can + * provide streams to use instead of `stdin` and `stderr`. + * + * Mainly usable for unit testing interactive input. + */ + SelectionList parseInteractive(int count, + TextInputStream *inputStream, + TextOutputStream *outputStream, + const std::string &context); /*! \brief * Parses selection(s) from a file. * diff --git a/src/gromacs/utility/exceptions.cpp b/src/gromacs/utility/exceptions.cpp index 14fe155e43..1248b96ef7 100644 --- a/src/gromacs/utility/exceptions.cpp +++ b/src/gromacs/utility/exceptions.cpp @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2011,2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2011,2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -60,6 +60,7 @@ #include "gromacs/utility/errorcodes.h" #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" #include "errorformat.h" @@ -308,6 +309,40 @@ class MessageWriterFileNoThrow : public MessageWriterInterface FILE *fp_; }; +/*! \brief + * Exception information writer to format into a TextOutputStream. + */ +class MessageWriterTextWriter : public MessageWriterInterface +{ + public: + //! Initializes a writer that writes to the given stream. + explicit MessageWriterTextWriter(TextWriter *writer) : writer_(writer) + { + } + + virtual void writeLine(const char *text, int indent) + { + // TODO: Line wrapping. + writer_->writeString(std::string(indent, ' ')); + writer_->writeLine(text); + } + virtual void writeErrNoInfo(int errorNumber, const char *funcName, + int indent) + { + writeLine(formatString("Reason: %s", std::strerror(errorNumber)).c_str(), + indent); + if (funcName != NULL) + { + writeLine(formatString("(call to %s() returned error code %d)", + funcName, errorNumber).c_str(), + indent); + } + } + + private: + TextWriter *writer_; +}; + /*! \brief * Exception information writer to format into an std::string. */ @@ -519,6 +554,13 @@ void formatExceptionMessageToFile(FILE *fp, const std::exception &ex) formatExceptionMessageInternal(&writer, ex, 0); } +void formatExceptionMessageToWriter(TextWriter *writer, + const std::exception &ex) +{ + MessageWriterTextWriter messageWriter(writer); + formatExceptionMessageInternal(&messageWriter, ex, 0); +} + int processExceptionAtExit(const std::exception & /*ex*/) { int returnCode = 1; diff --git a/src/gromacs/utility/exceptions.h b/src/gromacs/utility/exceptions.h index 40d723b8f5..493ee6bb60 100644 --- a/src/gromacs/utility/exceptions.h +++ b/src/gromacs/utility/exceptions.h @@ -60,6 +60,8 @@ namespace gmx { +class TextWriter; + namespace internal { //! Internal container type for storing a list of nested exceptions. @@ -444,6 +446,15 @@ std::string formatExceptionMessageToString(const std::exception &ex); * \throws std::bad_alloc if out of memory. */ void formatExceptionMessageToFile(FILE *fp, const std::exception &ex); +/*! \brief + * Formats an error message for reporting an exception. + * + * \param writer Writer to use for writing the message. + * \param[in] ex Exception to format. + * \throws std::bad_alloc if out of memory. + */ +void formatExceptionMessageToWriter(TextWriter *writer, + const std::exception &ex); /*! \brief * Handles an exception that is causing the program to terminate. * diff --git a/src/gromacs/utility/textwriter.cpp b/src/gromacs/utility/textwriter.cpp index 6e52e0a949..043261d529 100644 --- a/src/gromacs/utility/textwriter.cpp +++ b/src/gromacs/utility/textwriter.cpp @@ -77,6 +77,11 @@ TextWriter::TextWriter(const std::string &filename) { } +TextWriter::TextWriter(FILE *fp) + : impl_(new Impl(TextOutputStreamPointer(new TextOutputFile(fp)))) +{ +} + TextWriter::TextWriter(TextOutputStream *stream) : impl_(new Impl(TextOutputStreamPointer(stream, no_delete()))) { diff --git a/src/gromacs/utility/textwriter.h b/src/gromacs/utility/textwriter.h index de83c98d8a..a46446ed61 100644 --- a/src/gromacs/utility/textwriter.h +++ b/src/gromacs/utility/textwriter.h @@ -43,6 +43,8 @@ #ifndef GMX_UTILITY_TEXTWRITER_H #define GMX_UTILITY_TEXTWRITER_H +#include + #include #include "gromacs/utility/classhelpers.h" @@ -90,6 +92,21 @@ class TextWriter * a file, without the need to construct multiple objects. */ explicit TextWriter(const std::string &filename); + /*! \brief + * Creates a writer that writes to specified file. + * + * \param[in] fp File handle to write to. + * \throws std::bad_alloc if out of memory. + * \throws FileIOError on any I/O error. + * + * This constructor is provided for interoperability with C-like code + * for writing directly to an already opened file, without the need to + * construct multiple objects. + * + * The caller is responsible of closing \p fp; it is not allowed to + * call close() on the writer. + */ + explicit TextWriter(FILE *fp); /*! \brief * Creates a writer that writes to specified stream. * -- 2.22.0