Refactor for testing interactive selection input
[alexxy/gromacs.git] / src / gromacs / selection / selectioncollection.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2010,2011,2012,2013,2014,2015, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements gmx::SelectionCollection.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_selection
41  */
42 #include "gmxpre.h"
43
44 #include "selectioncollection.h"
45
46 #include <cctype>
47 #include <cstdio>
48
49 #include <string>
50 #include <vector>
51
52 #include <boost/scoped_ptr.hpp>
53 #include <boost/shared_ptr.hpp>
54
55 #include "gromacs/fileio/trx.h"
56 #include "gromacs/legacyheaders/oenv.h"
57 #include "gromacs/onlinehelp/helpmanager.h"
58 #include "gromacs/onlinehelp/helpwritercontext.h"
59 #include "gromacs/options/basicoptions.h"
60 #include "gromacs/options/options.h"
61 #include "gromacs/selection/selection.h"
62 #include "gromacs/selection/selhelp.h"
63 #include "gromacs/topology/topology.h"
64 #include "gromacs/utility/exceptions.h"
65 #include "gromacs/utility/filestream.h"
66 #include "gromacs/utility/gmxassert.h"
67 #include "gromacs/utility/smalloc.h"
68 #include "gromacs/utility/stringutil.h"
69 #include "gromacs/utility/textwriter.h"
70
71 #include "compiler.h"
72 #include "mempool.h"
73 #include "parser.h"
74 #include "poscalc.h"
75 #include "scanner.h"
76 #include "selectioncollection-impl.h"
77 #include "selelem.h"
78 #include "selmethod.h"
79 #include "symrec.h"
80
81 namespace gmx
82 {
83
84 /********************************************************************
85  * SelectionCollection::Impl
86  */
87
88 SelectionCollection::Impl::Impl()
89     : maxAtomIndex_(0), debugLevel_(0), bExternalGroupsSet_(false), grps_(NULL)
90 {
91     sc_.nvars     = 0;
92     sc_.varstrs   = NULL;
93     sc_.top       = NULL;
94     gmx_ana_index_clear(&sc_.gall);
95     sc_.mempool   = NULL;
96     sc_.symtab.reset(new SelectionParserSymbolTable);
97     gmx_ana_selmethod_register_defaults(sc_.symtab.get());
98 }
99
100
101 SelectionCollection::Impl::~Impl()
102 {
103     clearSymbolTable();
104     // The tree must be freed before the SelectionData objects, since the
105     // tree may hold references to the position data in SelectionData.
106     sc_.root.reset();
107     sc_.sel.clear();
108     for (int i = 0; i < sc_.nvars; ++i)
109     {
110         sfree(sc_.varstrs[i]);
111     }
112     sfree(sc_.varstrs);
113     gmx_ana_index_deinit(&sc_.gall);
114     if (sc_.mempool)
115     {
116         _gmx_sel_mempool_destroy(sc_.mempool);
117     }
118 }
119
120
121 void
122 SelectionCollection::Impl::clearSymbolTable()
123 {
124     sc_.symtab.reset();
125 }
126
127
128 namespace
129 {
130
131 /*! \brief
132  * Reads a single selection line from stdin.
133  *
134  * \param[in]  inputStream   Stream to read from (typically the StandardInputStream).
135  * \param[in]  statusWriter  Stream to print prompts to (if NULL, no output is done).
136  * \param[out] line          The read line in stored here.
137  * \returns true if something was read, false if at end of input.
138  *
139  * Handles line continuation, reading also the continuing line(s) in one call.
140  */
141 bool promptLine(TextInputStream *inputStream, TextWriter *statusWriter,
142                 std::string *line)
143 {
144     if (statusWriter != NULL)
145     {
146         statusWriter->writeString("> ");
147     }
148     if (!inputStream->readLine(line))
149     {
150         return false;
151     }
152     while (endsWith(*line, "\\\n"))
153     {
154         line->resize(line->length() - 2);
155         if (statusWriter != NULL)
156         {
157             statusWriter->writeString("... ");
158         }
159         std::string buffer;
160         // Return value ignored, buffer remains empty and works correctly
161         // if there is nothing to read.
162         inputStream->readLine(&buffer);
163         line->append(buffer);
164     }
165     if (endsWith(*line, "\n"))
166     {
167         line->resize(line->length() - 1);
168     }
169     else if (statusWriter != NULL)
170     {
171         statusWriter->writeLine();
172     }
173     return true;
174 }
175
176 /*! \brief
177  * Helper function for tokenizing the input and pushing them to the parser.
178  *
179  * \param     scanner       Tokenizer data structure.
180  * \param     parserState   Parser data structure.
181  * \param[in] bInteractive  Whether to operate in interactive mode.
182  *
183  * Repeatedly reads tokens using \p scanner and pushes them to the parser with
184  * \p parserState until there is no more input, or until enough input is given
185  * (only in interactive mode).
186  */
187 int runParserLoop(yyscan_t scanner, _gmx_sel_yypstate *parserState,
188                   bool bInteractive)
189 {
190     int status    = YYPUSH_MORE;
191     int prevToken = 0;
192     do
193     {
194         YYSTYPE value;
195         YYLTYPE location;
196         int     token = _gmx_sel_yylex(&value, &location, scanner);
197         if (bInteractive)
198         {
199             if (token == 0)
200             {
201                 break;
202             }
203             // Empty commands cause the interactive parser to print out
204             // status information. This avoids producing those unnecessarily,
205             // e.g., from "resname RA;;".
206             if (prevToken == CMD_SEP && token == CMD_SEP)
207             {
208                 continue;
209             }
210             prevToken = token;
211         }
212         status = _gmx_sel_yypush_parse(parserState, token, &value, &location, scanner);
213     }
214     while (status == YYPUSH_MORE);
215     _gmx_sel_lexer_rethrow_exception_if_occurred(scanner);
216     return status;
217 }
218
219 /*! \brief
220  * Print current status in response to empty line in interactive input.
221  *
222  * \param[in] writer         Writer to use for the output.
223  * \param[in] sc             Selection collection data structure.
224  * \param[in] grps           Available index groups.
225  * \param[in] firstSelection Index of first selection from this interactive
226  *     session.
227  * \param[in] maxCount       Maximum number of selections.
228  * \param[in] context        Context to print for what the selections are for.
229  * \param[in] bFirst         Whether this is the header that is printed before
230  *     any user input.
231  *
232  * Prints the available index groups and currently provided selections.
233  */
234 void printCurrentStatus(TextWriter *writer, gmx_ana_selcollection_t *sc,
235                         gmx_ana_indexgrps_t *grps, size_t firstSelection,
236                         int maxCount, const std::string &context, bool bFirst)
237 {
238     if (grps != NULL)
239     {
240         writer->writeLine("Available static index groups:");
241         gmx_ana_indexgrps_print(writer, grps, 0);
242     }
243     writer->writeString("Specify ");
244     if (maxCount < 0)
245     {
246         writer->writeString("any number of selections");
247     }
248     else if (maxCount == 1)
249     {
250         writer->writeString("a selection");
251     }
252     else
253     {
254         writer->writeString(formatString("%d selections", maxCount));
255     }
256     writer->writeString(formatString("%s%s:\n",
257                                      context.empty() ? "" : " ", context.c_str()));
258     writer->writeString(formatString(
259                                 "(one per line, <enter> for status/groups, 'help' for help%s)\n",
260                                 maxCount < 0 ? ", Ctrl-D to end" : ""));
261     if (!bFirst && (sc->nvars > 0 || sc->sel.size() > firstSelection))
262     {
263         writer->writeLine("Currently provided selections:");
264         for (int i = 0; i < sc->nvars; ++i)
265         {
266             writer->writeString(formatString("     %s\n", sc->varstrs[i]));
267         }
268         for (size_t i = firstSelection; i < sc->sel.size(); ++i)
269         {
270             writer->writeString(formatString(
271                                         " %2d. %s\n",
272                                         static_cast<int>(i - firstSelection + 1),
273                                         sc->sel[i]->selectionText()));
274         }
275         if (maxCount > 0)
276         {
277             const int remaining
278                 = maxCount - static_cast<int>(sc->sel.size() - firstSelection);
279             writer->writeString(formatString(
280                                         "(%d more selection%s required)\n",
281                                         remaining, remaining > 1 ? "s" : ""));
282         }
283     }
284 }
285
286 /*! \brief
287  * Prints selection help in interactive selection input.
288  *
289  * \param[in] writer Writer to use for the output.
290  * \param[in] sc    Selection collection data structure.
291  * \param[in] line  Line of user input requesting help (starting with `help`).
292  *
293  * Initializes the selection help if not yet initialized, and finds the help
294  * topic based on words on the input line.
295  */
296 void printHelp(TextWriter *writer, gmx_ana_selcollection_t *sc,
297                const std::string &line)
298 {
299     if (sc->rootHelp.get() == NULL)
300     {
301         sc->rootHelp = createSelectionHelpTopic();
302     }
303     HelpWriterContext context(&writer->stream(), eHelpOutputFormat_Console);
304     HelpManager       manager(*sc->rootHelp, context);
305     try
306     {
307         std::vector<std::string>                 topic = splitString(line);
308         std::vector<std::string>::const_iterator value;
309         // First item in the list is the 'help' token.
310         for (value = topic.begin() + 1; value != topic.end(); ++value)
311         {
312             manager.enterTopic(*value);
313         }
314     }
315     catch (const InvalidInputError &ex)
316     {
317         writer->writeLine(ex.what());
318         return;
319     }
320     manager.writeCurrentTopic();
321 }
322
323 /*! \brief
324  * Helper function that runs the parser once the tokenizer has been
325  * initialized.
326  *
327  * \param[in,out] scanner       Scanner data structure.
328  * \param[in]     inputStream   Stream to use for input (currently only with
329  *      `bInteractive==true`).
330  * \param[in]     bInteractive  Whether to use a line-based reading
331  *      algorithm designed for interactive input.
332  * \param[in]     maxnr   Maximum number of selections to parse
333  *      (if -1, parse as many as provided by the user).
334  * \param[in]     context Context to print for what the selections are for.
335  * \returns       Vector of parsed selections.
336  * \throws        std::bad_alloc if out of memory.
337  * \throws        InvalidInputError if there is a parsing error.
338  *
339  * Used internally to implement parseInteractive(), parseFromFile() and
340  * parseFromString().
341  */
342 SelectionList runParser(yyscan_t scanner, TextInputStream *inputStream,
343                         bool bInteractive, int maxnr, const std::string &context)
344 {
345     boost::shared_ptr<void>  scannerGuard(scanner, &_gmx_sel_free_lexer);
346     gmx_ana_selcollection_t *sc   = _gmx_sel_lexer_selcollection(scanner);
347     gmx_ana_indexgrps_t     *grps = _gmx_sel_lexer_indexgrps(scanner);
348
349     size_t                   oldCount = sc->sel.size();
350     {
351         boost::shared_ptr<_gmx_sel_yypstate> parserState(
352                 _gmx_sel_yypstate_new(), &_gmx_sel_yypstate_delete);
353         if (bInteractive)
354         {
355             TextWriter *statusWriter = _gmx_sel_lexer_get_status_writer(scanner);
356             if (statusWriter != NULL)
357             {
358                 printCurrentStatus(statusWriter, sc, grps, oldCount, maxnr, context, true);
359             }
360             std::string line;
361             int         status;
362             while (promptLine(inputStream, statusWriter, &line))
363             {
364                 if (statusWriter != NULL)
365                 {
366                     line = stripString(line);
367                     if (line.empty())
368                     {
369                         printCurrentStatus(statusWriter, sc, grps, oldCount, maxnr, context, false);
370                         continue;
371                     }
372                     if (startsWith(line, "help")
373                         && (line[4] == 0 || std::isspace(line[4])))
374                     {
375                         printHelp(statusWriter, sc, line);
376                         continue;
377                     }
378                 }
379                 line.append("\n");
380                 _gmx_sel_set_lex_input_str(scanner, line.c_str());
381                 status = runParserLoop(scanner, parserState.get(), true);
382                 if (status != YYPUSH_MORE)
383                 {
384                     // TODO: Check if there is more input, and issue an
385                     // error/warning if some input was ignored.
386                     goto early_termination;
387                 }
388             }
389             {
390                 YYLTYPE location;
391                 status = _gmx_sel_yypush_parse(parserState.get(), 0, NULL,
392                                                &location, scanner);
393             }
394             // TODO: Remove added selections from the collection if parsing failed?
395             _gmx_sel_lexer_rethrow_exception_if_occurred(scanner);
396 early_termination:
397             GMX_RELEASE_ASSERT(status == 0,
398                                "Parser errors should have resulted in an exception");
399         }
400         else
401         {
402             int status = runParserLoop(scanner, parserState.get(), false);
403             GMX_RELEASE_ASSERT(status == 0,
404                                "Parser errors should have resulted in an exception");
405         }
406     }
407     scannerGuard.reset();
408     int nr = sc->sel.size() - oldCount;
409     if (maxnr > 0 && nr != maxnr)
410     {
411         std::string message
412             = formatString("Too few selections provided; got %d, expected %d",
413                            nr, maxnr);
414         GMX_THROW(InvalidInputError(message));
415     }
416
417     SelectionList                     result;
418     SelectionDataList::const_iterator i;
419     result.reserve(nr);
420     for (i = sc->sel.begin() + oldCount; i != sc->sel.end(); ++i)
421     {
422         result.push_back(Selection(i->get()));
423     }
424     return result;
425 }
426
427 /*! \brief
428  * Checks that index groups have valid atom indices.
429  *
430  * \param[in]    root    Root of selection tree to process.
431  * \param[in]    natoms  Maximum number of atoms that the selections are set
432  *     to evaluate.
433  * \param        errors  Object for reporting any error messages.
434  * \throws std::bad_alloc if out of memory.
435  *
436  * Recursively checks the selection tree for index groups.
437  * Each found group is checked that it only contains atom indices that match
438  * the topology/maximum number of atoms set for the selection collection.
439  * Any issues are reported to \p errors.
440  */
441 void checkExternalGroups(const SelectionTreeElementPointer &root,
442                          int                                natoms,
443                          ExceptionInitializer              *errors)
444 {
445     if (root->type == SEL_CONST && root->v.type == GROUP_VALUE)
446     {
447         try
448         {
449             root->checkIndexGroup(natoms);
450         }
451         catch (const UserInputError &)
452         {
453             errors->addCurrentExceptionAsNested();
454         }
455     }
456
457     SelectionTreeElementPointer child = root->child;
458     while (child)
459     {
460         checkExternalGroups(child, natoms, errors);
461         child = child->next;
462     }
463 }
464
465 }   // namespace
466
467
468 void SelectionCollection::Impl::resolveExternalGroups(
469         const SelectionTreeElementPointer &root,
470         ExceptionInitializer              *errors)
471 {
472
473     if (root->type == SEL_GROUPREF)
474     {
475         try
476         {
477             root->resolveIndexGroupReference(grps_, sc_.gall.isize);
478         }
479         catch (const UserInputError &)
480         {
481             errors->addCurrentExceptionAsNested();
482         }
483     }
484
485     SelectionTreeElementPointer child = root->child;
486     while (child)
487     {
488         resolveExternalGroups(child, errors);
489         root->flags |= (child->flags & SEL_UNSORTED);
490         child        = child->next;
491     }
492 }
493
494
495 /********************************************************************
496  * SelectionCollection
497  */
498
499 SelectionCollection::SelectionCollection()
500     : impl_(new Impl)
501 {
502 }
503
504
505 SelectionCollection::~SelectionCollection()
506 {
507 }
508
509
510 void
511 SelectionCollection::initOptions(Options *options)
512 {
513     const char * const debug_levels[]
514         = { "no", "basic", "compile", "eval", "full" };
515
516     bool bAllowNonAtomOutput = false;
517     SelectionDataList::const_iterator iter;
518     for (iter = impl_->sc_.sel.begin(); iter != impl_->sc_.sel.end(); ++iter)
519     {
520         const internal::SelectionData &sel = **iter;
521         if (!sel.hasFlag(efSelection_OnlyAtoms))
522         {
523             bAllowNonAtomOutput = true;
524         }
525     }
526
527     const char *const *postypes = PositionCalculationCollection::typeEnumValues;
528     options->addOption(StringOption("selrpos")
529                            .enumValueFromNullTerminatedArray(postypes)
530                            .store(&impl_->rpost_).defaultValue(postypes[0])
531                            .description("Selection reference positions"));
532     if (bAllowNonAtomOutput)
533     {
534         options->addOption(StringOption("seltype")
535                                .enumValueFromNullTerminatedArray(postypes)
536                                .store(&impl_->spost_).defaultValue(postypes[0])
537                                .description("Default selection output positions"));
538     }
539     else
540     {
541         impl_->spost_ = postypes[0];
542     }
543     GMX_RELEASE_ASSERT(impl_->debugLevel_ >= 0 && impl_->debugLevel_ <= 4,
544                        "Debug level out of range");
545     options->addOption(StringOption("seldebug").hidden(impl_->debugLevel_ == 0)
546                            .enumValue(debug_levels)
547                            .defaultValue(debug_levels[impl_->debugLevel_])
548                            .storeEnumIndex(&impl_->debugLevel_)
549                            .description("Print out selection trees for debugging"));
550 }
551
552
553 void
554 SelectionCollection::setReferencePosType(const char *type)
555 {
556     GMX_RELEASE_ASSERT(type != NULL, "Cannot assign NULL position type");
557     // Check that the type is valid, throw if it is not.
558     e_poscalc_t  dummytype;
559     int          dummyflags;
560     PositionCalculationCollection::typeFromEnum(type, &dummytype, &dummyflags);
561     impl_->rpost_ = type;
562 }
563
564
565 void
566 SelectionCollection::setOutputPosType(const char *type)
567 {
568     GMX_RELEASE_ASSERT(type != NULL, "Cannot assign NULL position type");
569     // Check that the type is valid, throw if it is not.
570     e_poscalc_t  dummytype;
571     int          dummyflags;
572     PositionCalculationCollection::typeFromEnum(type, &dummytype, &dummyflags);
573     impl_->spost_ = type;
574 }
575
576
577 void
578 SelectionCollection::setDebugLevel(int debugLevel)
579 {
580     impl_->debugLevel_ = debugLevel;
581 }
582
583
584 void
585 SelectionCollection::setTopology(t_topology *top, int natoms)
586 {
587     GMX_RELEASE_ASSERT(natoms > 0 || top != NULL,
588                        "The number of atoms must be given if there is no topology");
589     // Get the number of atoms from the topology if it is not given.
590     if (natoms <= 0)
591     {
592         natoms = top->atoms.nr;
593     }
594     if (impl_->bExternalGroupsSet_)
595     {
596         ExceptionInitializer        errors("Invalid index group references encountered");
597         SelectionTreeElementPointer root = impl_->sc_.root;
598         while (root)
599         {
600             checkExternalGroups(root, natoms, &errors);
601             root = root->next;
602         }
603         if (errors.hasNestedExceptions())
604         {
605             GMX_THROW(InconsistentInputError(errors));
606         }
607     }
608     gmx_ana_selcollection_t *sc = &impl_->sc_;
609     // Do this first, as it allocates memory, while the others don't throw.
610     gmx_ana_index_init_simple(&sc->gall, natoms);
611     sc->pcc.setTopology(top);
612     sc->top = top;
613 }
614
615
616 void
617 SelectionCollection::setIndexGroups(gmx_ana_indexgrps_t *grps)
618 {
619     GMX_RELEASE_ASSERT(grps == NULL || !impl_->bExternalGroupsSet_,
620                        "Can only set external groups once or clear them afterwards");
621     impl_->grps_               = grps;
622     impl_->bExternalGroupsSet_ = true;
623
624     ExceptionInitializer        errors("Invalid index group reference(s)");
625     SelectionTreeElementPointer root = impl_->sc_.root;
626     while (root)
627     {
628         impl_->resolveExternalGroups(root, &errors);
629         root->checkUnsortedAtoms(true, &errors);
630         root = root->next;
631     }
632     if (errors.hasNestedExceptions())
633     {
634         GMX_THROW(InconsistentInputError(errors));
635     }
636     for (size_t i = 0; i < impl_->sc_.sel.size(); ++i)
637     {
638         impl_->sc_.sel[i]->refreshName();
639     }
640 }
641
642
643 bool
644 SelectionCollection::requiresTopology() const
645 {
646     e_poscalc_t  type;
647     int          flags;
648
649     if (!impl_->rpost_.empty())
650     {
651         flags = 0;
652         // Should not throw, because has been checked earlier.
653         PositionCalculationCollection::typeFromEnum(impl_->rpost_.c_str(),
654                                                     &type, &flags);
655         if (type != POS_ATOM)
656         {
657             return true;
658         }
659     }
660     if (!impl_->spost_.empty())
661     {
662         flags = 0;
663         // Should not throw, because has been checked earlier.
664         PositionCalculationCollection::typeFromEnum(impl_->spost_.c_str(),
665                                                     &type, &flags);
666         if (type != POS_ATOM)
667         {
668             return true;
669         }
670     }
671
672     SelectionTreeElementPointer sel = impl_->sc_.root;
673     while (sel)
674     {
675         if (_gmx_selelem_requires_top(*sel))
676         {
677             return true;
678         }
679         sel = sel->next;
680     }
681     return false;
682 }
683
684 SelectionList
685 SelectionCollection::parseFromStdin(int count, bool bInteractive,
686                                     const std::string &context)
687 {
688     return parseInteractive(count, &StandardInputStream::instance(),
689                             bInteractive ? &TextOutputFile::standardError() : NULL,
690                             context);
691 }
692
693 SelectionList
694 SelectionCollection::parseInteractive(int                count,
695                                       TextInputStream   *inputStream,
696                                       TextOutputStream  *statusStream,
697                                       const std::string &context)
698 {
699     yyscan_t scanner;
700
701     boost::scoped_ptr<TextWriter> statusWriter;
702     if (statusStream != NULL)
703     {
704         statusWriter.reset(new TextWriter(statusStream));
705     }
706     _gmx_sel_init_lexer(&scanner, &impl_->sc_, statusWriter.get(),
707                         count, impl_->bExternalGroupsSet_, impl_->grps_);
708     return runParser(scanner, inputStream, true, count, context);
709 }
710
711
712 SelectionList
713 SelectionCollection::parseFromFile(const std::string &filename)
714 {
715
716     try
717     {
718         yyscan_t      scanner;
719         TextInputFile file(filename);
720         // TODO: Exception-safe way of using the lexer.
721         _gmx_sel_init_lexer(&scanner, &impl_->sc_, NULL, -1,
722                             impl_->bExternalGroupsSet_,
723                             impl_->grps_);
724         _gmx_sel_set_lex_input_file(scanner, file.handle());
725         return runParser(scanner, NULL, false, -1, std::string());
726     }
727     catch (GromacsException &ex)
728     {
729         ex.prependContext(formatString(
730                                   "Error in parsing selections from file '%s'",
731                                   filename.c_str()));
732         throw;
733     }
734 }
735
736
737 SelectionList
738 SelectionCollection::parseFromString(const std::string &str)
739 {
740     yyscan_t scanner;
741
742     _gmx_sel_init_lexer(&scanner, &impl_->sc_, NULL, -1,
743                         impl_->bExternalGroupsSet_,
744                         impl_->grps_);
745     _gmx_sel_set_lex_input_str(scanner, str.c_str());
746     return runParser(scanner, NULL, false, -1, std::string());
747 }
748
749
750 void
751 SelectionCollection::compile()
752 {
753     if (impl_->sc_.top == NULL && requiresTopology())
754     {
755         GMX_THROW(InconsistentInputError("Selection requires topology information, but none provided"));
756     }
757     if (!impl_->bExternalGroupsSet_)
758     {
759         setIndexGroups(NULL);
760     }
761     if (impl_->debugLevel_ >= 1)
762     {
763         printTree(stderr, false);
764     }
765
766     SelectionCompiler compiler;
767     compiler.compile(this);
768
769     if (impl_->debugLevel_ >= 1)
770     {
771         std::fprintf(stderr, "\n");
772         printTree(stderr, false);
773         std::fprintf(stderr, "\n");
774         impl_->sc_.pcc.printTree(stderr);
775         std::fprintf(stderr, "\n");
776     }
777     impl_->sc_.pcc.initEvaluation();
778     if (impl_->debugLevel_ >= 1)
779     {
780         impl_->sc_.pcc.printTree(stderr);
781         std::fprintf(stderr, "\n");
782     }
783
784     // TODO: It would be nicer to associate the name of the selection option
785     // (if available) to the error message.
786     SelectionDataList::const_iterator iter;
787     for (iter = impl_->sc_.sel.begin(); iter != impl_->sc_.sel.end(); ++iter)
788     {
789         const internal::SelectionData &sel = **iter;
790         if (sel.hasFlag(efSelection_OnlyAtoms))
791         {
792             if (!sel.hasOnlyAtoms())
793             {
794                 std::string message = formatString(
795                             "Selection '%s' does not evaluate to individual atoms. "
796                             "This is not allowed in this context.",
797                             sel.selectionText());
798                 GMX_THROW(InvalidInputError(message));
799             }
800         }
801         if (sel.hasFlag(efSelection_DisallowEmpty))
802         {
803             if (sel.posCount() == 0)
804             {
805                 std::string message = formatString(
806                             "Selection '%s' never matches any atoms.",
807                             sel.selectionText());
808                 GMX_THROW(InvalidInputError(message));
809             }
810         }
811     }
812 }
813
814
815 void
816 SelectionCollection::evaluate(t_trxframe *fr, t_pbc *pbc)
817 {
818     if (fr->natoms <= impl_->maxAtomIndex_)
819     {
820         std::string message = formatString(
821                     "Trajectory has less atoms (%d) than what is required for "
822                     "evaluating the provided selections (atoms up to index %d "
823                     "are required).", fr->natoms, impl_->maxAtomIndex_ + 1);
824         GMX_THROW(InconsistentInputError(message));
825     }
826     impl_->sc_.pcc.initFrame();
827
828     SelectionEvaluator evaluator;
829     evaluator.evaluate(this, fr, pbc);
830
831     if (impl_->debugLevel_ >= 3)
832     {
833         std::fprintf(stderr, "\n");
834         printTree(stderr, true);
835     }
836 }
837
838
839 void
840 SelectionCollection::evaluateFinal(int nframes)
841 {
842     SelectionEvaluator evaluator;
843     evaluator.evaluateFinal(this, nframes);
844 }
845
846
847 void
848 SelectionCollection::printTree(FILE *fp, bool bValues) const
849 {
850     SelectionTreeElementPointer sel = impl_->sc_.root;
851     while (sel)
852     {
853         _gmx_selelem_print_tree(fp, *sel, bValues, 0);
854         sel = sel->next;
855     }
856 }
857
858
859 void
860 SelectionCollection::printXvgrInfo(FILE *out, output_env_t oenv) const
861 {
862     if (output_env_get_xvg_format(oenv) != exvgNONE)
863     {
864         const gmx_ana_selcollection_t &sc = impl_->sc_;
865         std::fprintf(out, "# Selections:\n");
866         for (int i = 0; i < sc.nvars; ++i)
867         {
868             std::fprintf(out, "#   %s\n", sc.varstrs[i]);
869         }
870         for (size_t i = 0; i < sc.sel.size(); ++i)
871         {
872             std::fprintf(out, "#   %s\n", sc.sel[i]->selectionText());
873         }
874         std::fprintf(out, "#\n");
875     }
876 }
877
878 } // namespace gmx