Refactor for testing interactive selection input
authorTeemu Murtola <teemu.murtola@gmail.com>
Mon, 29 Jun 2015 04:58:04 +0000 (07:58 +0300)
committerTeemu Murtola <teemu.murtola@gmail.com>
Sat, 4 Jul 2015 14:57:27 +0000 (16:57 +0200)
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

15 files changed:
src/gromacs/selection/indexutil.cpp
src/gromacs/selection/indexutil.h
src/gromacs/selection/parsetree.cpp
src/gromacs/selection/scanner.cpp
src/gromacs/selection/scanner.h
src/gromacs/selection/scanner.l
src/gromacs/selection/scanner_internal.cpp
src/gromacs/selection/scanner_internal.h
src/gromacs/selection/selection.cpp
src/gromacs/selection/selectioncollection.cpp
src/gromacs/selection/selectioncollection.h
src/gromacs/utility/exceptions.cpp
src/gromacs/utility/exceptions.h
src/gromacs/utility/textwriter.cpp
src/gromacs/utility/textwriter.h

index 2e85724ac4d8e43ee679f2e55269a643fb5c03eb..8aecd065e7ed9199bc4e74abb0a4d0d4e245aab9 100644 (file)
@@ -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
index 7ec5bd96916b0d2610fcedbe87cd9ab2868b4784..9ac9364008ba028178a82269af688450ca373f41 100644 (file)
@@ -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.
 #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.
index 79162acd922aeeef53b394c5a21a6fd3363eaaea..d1eb39fcf3081e1f5e1769c12b36677cea8d2f9e 100644 (file)
@@ -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
 #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;
 }
index a37f3e8cf77fd65b7dd0ab6913d1c72cd78eeb24..3cdfb93e73b11554cb8984336cb319257c61e377 100644 (file)
@@ -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;
index ad7fcf56ce3e33a90ec1e620803edb1bdc5bd388..b918ccc5fd375f9266eb75d7a7f3378505bbfef1 100644 (file)
 
 #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);
index f41234b2de01ea6d47b9acfe7560e89d1990633c..d2d2fd97ecbd8bc7edcfd52df34ceeaf32a89d0d 100644 (file)
@@ -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;
index 1f3be505e5d192e406ce9824d30b916a1942ad54..d66d647b58dfb524cc635ae20429cea8ae68ccdc 100644 (file)
@@ -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<int>(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 *
index 5bd8eb0e433718259cf383c4135cef7a1894af1b..7b01b81e27346ea116fa2a2d81c63e51ab1cb9ea 100644 (file)
@@ -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;
index 7b77d5c5ac4845a31d286e680517d69395f48180..8c81ca51ac6feba05cfc00d6d9d67b2b14f6f969 100644 (file)
@@ -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)
index aba20c77174cc6c50f82f1c699634bca79431f06..bbe3f11df267e7b05191d1c847b3823bfb9ab351 100644 (file)
@@ -49,6 +49,7 @@
 #include <string>
 #include <vector>
 
+#include <boost/scoped_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 
 #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, <enter> 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, <enter> 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<int>(i - firstSelection + 1),
-                         sc->sel[i]->selectionText());
+            writer->writeString(formatString(
+                                        " %2d. %s\n",
+                                        static_cast<int>(i - firstSelection + 1),
+                                        sc->sel[i]->selectionText()));
         }
         if (maxCount > 0)
         {
             const int remaining
                 = maxCount - static_cast<int>(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<void>  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<TextWriter> 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());
 }
 
 
index 8ec7ed1f230554acb65ed5cbbbbb094be6af8a80..826f20a04d1a85c3a8ddd23f6e2afda3995d8ec4 100644 (file)
@@ -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.
          *
index 14fe155e434e892d378d644b6eecefa24b8e4377..1248b96ef7bec6b3933b59dcf2f9f92fed33f569 100644 (file)
@@ -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;
index 40d723b8f516190447e1de215306c071d46d9a0d..493ee6bb6052a5b8d13e3de8d4318a24e57193c1 100644 (file)
@@ -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.
  *
index 6e52e0a949c35c23d78f3331941b5ff71f84c011..043261d529c58a3f92d1ba8781fc1a03167f1400 100644 (file)
@@ -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<TextOutputStream>())))
 {
index de83c98d8a9e609d8d08b216a628a209c18e6b89..a46446ed617eb9dfffc219c4957f63b6614b586d 100644 (file)
@@ -43,6 +43,8 @@
 #ifndef GMX_UTILITY_TEXTWRITER_H
 #define GMX_UTILITY_TEXTWRITER_H
 
+#include <cstdio>
+
 #include <string>
 
 #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.
          *