clang-tidy modernize
[gromacs.git] / src / gromacs / selection / selmethod.cpp
blob17bc002e47f546ecfef732459d730d463a4a2fed
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2009,2010,2011,2012,2014,2015,2017,2018, 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.
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.
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.
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.
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.
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.
35 /*! \internal \file
36 * \brief
37 * Implements functions in selmethod.h.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_selection
42 #include "gmxpre.h"
44 #include "selmethod.h"
46 #include <cctype>
47 #include <cstdarg>
49 #include "gromacs/utility/arraysize.h"
50 #include "gromacs/utility/cstringutil.h"
51 #include "gromacs/utility/exceptions.h"
53 #include "selmethod-impl.h"
54 #include "symrec.h"
56 /*! \internal \brief
57 * Helper structure for defining selection methods.
59 typedef struct {
60 /*! \brief
61 * Name to register the method under.
63 * If NULL, use the actual name of the method.
64 * This field is used for defining synonyms.
66 const char *name;
67 /** Method data structure to register. */
68 gmx_ana_selmethod_t *method;
69 } t_register_method;
71 /*! \brief
72 * Convenience function for reporting errors found in selection methods.
74 static void
75 report_error(FILE *fp, const char *name, const char *fmt, ...)
77 va_list ap;
78 va_start(ap, fmt);
79 if (fp)
81 fprintf(fp, "selection method '%s': ", name);
82 vfprintf(fp, fmt, ap);
83 fprintf(fp, "\n");
85 va_end(ap);
88 /*! \brief
89 * Convenience function for reporting errors found in selection method parameters.
91 static void
92 report_param_error(FILE *fp, const char *mname, const char *pname,
93 const char *fmt, ...)
95 va_list ap;
96 va_start(ap, fmt);
97 if (fp)
99 fprintf(fp, "selection method '%s': parameter '%s': ", mname, pname);
100 vfprintf(fp, fmt, ap);
101 fprintf(fp, "\n");
103 va_end(ap);
106 /*! \brief
107 * Checks the validity of parameters.
109 * \param[in] fp File handle to use for diagnostic messages
110 * (can be NULL).
111 * \param[in] name Name of the method (used for error messages).
112 * \param[in] nparams Number of parameters in \p param.
113 * \param[in,out] param Parameter array
114 * (only the \c flags field of boolean parameters may be modified).
115 * \param[in] symtab Symbol table (used for checking overlaps).
116 * \returns true if there are no problems with the parameters,
117 * false otherwise.
119 * This function performs some checks common to both check_method() and
120 * check_modifier().
121 * The purpose of these checks is to ensure that the selection parser does not
122 * need to check for the validity of the parameters at each turn, and to
123 * report programming errors as early as possible.
124 * If you remove a check, make sure that the parameter parser can handle the
125 * resulting parameters.
127 static bool
128 check_params(FILE *fp, const char *name, int nparams, gmx_ana_selparam_t param[],
129 const gmx::SelectionParserSymbolTable &symtab)
131 bool bOk = true;
132 int i, j;
134 if (nparams > 0 && !param)
136 report_error(fp, name, "error: missing parameter data");
137 return false;
139 if (nparams == 0 && param)
141 report_error(fp, name, "warning: parameter data unused because nparams=0");
143 /* Check each parameter */
144 for (i = 0; i < nparams; ++i)
146 /* Check that there is at most one NULL name, in the beginning */
147 if (param[i].name == nullptr && i > 0)
149 report_error(fp, name, "error: NULL parameter should be the first one");
150 bOk = false;
151 continue;
153 /* Check for duplicates */
154 for (j = 0; j < i; ++j)
156 if (param[j].name == nullptr)
158 continue;
160 if (!gmx_strcasecmp(param[i].name, param[j].name))
162 report_error(fp, name, "error: duplicate parameter name '%s'", param[i].name);
163 bOk = false;
164 break;
167 /* Check flags */
168 if (param[i].flags & SPAR_SET)
170 report_param_error(fp, name, param[i].name, "warning: flag SPAR_SET is set");
171 param[i].flags &= ~SPAR_SET;
173 if (param[i].flags & SPAR_RANGES)
175 if (param[i].val.type != INT_VALUE && param[i].val.type != REAL_VALUE)
177 report_param_error(fp, name, param[i].name, "error: SPAR_RANGES cannot be set for a non-numeric parameter");
178 bOk = false;
180 if (param[i].flags & SPAR_DYNAMIC)
182 report_param_error(fp, name, param[i].name, "warning: SPAR_DYNAMIC does not have effect with SPAR_RANGES");
183 param[i].flags &= ~SPAR_DYNAMIC;
185 if (!(param[i].flags & SPAR_VARNUM) && param[i].val.nr != 1)
187 report_param_error(fp, name, param[i].name, "error: range should take either one or an arbitrary number of values");
188 bOk = false;
190 if (param[i].flags & SPAR_ATOMVAL)
192 report_param_error(fp, name, param[i].name, "error: SPAR_RANGES and SPAR_ATOMVAL both set");
193 bOk = false;
196 if ((param[i].flags & SPAR_VARNUM) && (param[i].flags & SPAR_ATOMVAL))
198 report_param_error(fp, name, param[i].name, "error: SPAR_VARNUM and SPAR_ATOMVAL both set");
199 bOk = false;
201 if (param[i].flags & SPAR_ENUMVAL)
203 if (param[i].val.type != STR_VALUE)
205 report_param_error(fp, name, param[i].name, "error: SPAR_ENUMVAL can only be set for string parameters");
206 bOk = false;
208 if (param[i].val.nr != 1)
210 report_param_error(fp, name, param[i].name, "error: SPAR_ENUMVAL parameters should take exactly one value");
211 bOk = false;
213 if (param[i].flags & (SPAR_DYNAMIC | SPAR_VARNUM | SPAR_ATOMVAL))
215 report_param_error(fp, name, param[i].name, "error: only SPAR_OPTIONAL supported with SPAR_ENUMVAL");
216 bOk = false;
219 /* Check boolean parameters */
220 if (param[i].val.type == NO_VALUE)
222 if (param[i].val.nr != 0)
224 report_param_error(fp, name, param[i].name, "error: number of values should be zero for boolean parameters");
225 bOk = false;
227 /* The boolean parameters should always be optional, so set the
228 * flag for convenience. */
229 param[i].flags |= SPAR_OPTIONAL;
230 /* Any other flags should not be specified */
231 if (param[i].flags & ~SPAR_OPTIONAL)
233 report_param_error(fp, name, param[i].name, "error: boolean parameter should not have any flags set");
234 bOk = false;
237 /* Check val.nr */
238 if (param[i].flags & (SPAR_VARNUM | SPAR_ATOMVAL))
240 if (param[i].val.nr != -1)
242 report_param_error(fp, name, param[i].name, "warning: val.nr is not -1 although SPAR_VARNUM/SPAR_ATOMVAL is set");
244 param[i].val.nr = -1;
246 else if (param[i].val.type != NO_VALUE)
248 if (param[i].val.nr <= 0)
250 report_param_error(fp, name, param[i].name, "error: val.nr <= 0");
251 bOk = false;
254 /* Check that the value pointer is NULL */
255 if (param[i].nvalptr != nullptr)
257 report_param_error(fp, name, param[i].name, "warning: nvalptr is set");
259 if (param[i].val.u.ptr != nullptr && !(param[i].flags & SPAR_ENUMVAL))
261 report_param_error(fp, name, param[i].name, "warning: value pointer is set");
263 /* Check that the name contains only valid characters */
264 if (param[i].name == nullptr)
266 continue;
268 if (!isalpha(param[i].name[0]))
270 report_param_error(fp, name, param[i].name, "error: name does not begin with a letter");
271 bOk = false;
272 continue;
274 for (j = 1; param[i].name[j] != 0; ++j)
276 if (param[i].name[j] != '_' && !isalnum(param[i].name[j]))
278 report_param_error(fp, name, param[i].name, "error: name contains non-alphanumeric characters");
279 bOk = false;
280 break;
283 if (param[i].name[j] != 0)
285 continue;
287 /* Check that the name does not conflict with a method */
288 if (symtab.findSymbol(param[i].name))
290 report_param_error(fp, name, param[i].name, "error: name conflicts with another method or a keyword");
291 bOk = false;
293 } /* End of parameter loop */
294 /* Check parameters of existing methods */
295 gmx::SelectionParserSymbolIterator symbol
296 = symtab.beginIterator(gmx::SelectionParserSymbol::MethodSymbol);
297 while (symbol != symtab.endIterator())
299 gmx_ana_selmethod_t *method = symbol->methodValue();
300 gmx_ana_selparam_t *param =
301 gmx_ana_selmethod_find_param(name, method);
302 if (param)
304 report_param_error(fp, method->name, param->name, "error: name conflicts with another method or a keyword");
305 bOk = false;
307 ++symbol;
309 return bOk;
312 /*! \brief
313 * Checks the validity of selection method callback functions.
315 * \param[in] fp File handle to use for diagnostic messages
316 * (can be NULL).
317 * \param[in] method The method to check.
318 * \returns true if there are no problems, false otherwise.
320 * This function performs some checks common to both check_method() and
321 * check_modifier().
322 * This function checks that all the required callbacks are defined, i.e.,
323 * not NULL, to find programming errors.
325 static bool
326 check_callbacks(FILE *fp, gmx_ana_selmethod_t *method)
328 bool bOk = true;
329 bool bNeedInit;
330 int i;
332 /* Make some checks on init_data and free */
333 if (method->nparams > 0 && !method->init_data)
335 report_error(fp, method->name, "error: init_data should be provided because the method has parameters");
336 bOk = false;
338 if (method->free && !method->init_data)
340 report_error(fp, method->name, "warning: free is not used because of missing init_data");
342 /* Check presence of outinit for position-valued methods */
343 if (method->type == POS_VALUE && !method->outinit)
345 report_error(fp, method->name, "error: outinit should be provided because the method has POS_VALUE");
346 bOk = false;
348 /* Check presence of outinit for variable output count methods */
349 if ((method->flags & SMETH_VARNUMVAL) && !method->outinit)
351 report_error(fp, method->name, "error: outinit should be provided because the method has SMETH_VARNUMVAL");
352 bOk = false;
354 /* Warn of dynamic callbacks in static methods */
355 if (!(method->flags & SMETH_MODIFIER))
357 if (method->pupdate && !(method->flags & SMETH_DYNAMIC))
359 report_error(fp, method->name, "warning: pupdate not used because the method is static");
360 method->pupdate = nullptr;
363 /* Check that there is an evaluation function */
364 if (method->type != NO_VALUE && !method->update && !method->pupdate)
366 report_error(fp, method->name, "error: evaluation function missing");
367 bOk = false;
369 /* Loop through the parameters to determine if initialization callbacks
370 * are needed. */
371 bNeedInit = false;
372 for (i = 0; i < method->nparams; ++i)
374 if (method->param[i].val.type != POS_VALUE
375 && (method->param[i].flags & (SPAR_VARNUM | SPAR_ATOMVAL)))
377 bNeedInit = true;
380 /* Check that the callbacks required by the parameters are present */
381 if (bNeedInit && !method->init)
383 report_error(fp, method->name, "error: init should be provided");
384 bOk = false;
386 return bOk;
389 /*! \brief
390 * Checks the validity of a selection method.
392 * \param[in] fp File handle to use for diagnostic messages
393 * (can be NULL).
394 * \param[in,out] method Method to check.
395 * \param[in] symtab Symbol table (used for checking overlaps).
397 * Checks the validity of the given selection method data structure
398 * that does not have \ref SMETH_MODIFIER set.
399 * If you remove a check, please make sure that the selection parser,
400 * compiler, and evaluation functions can deal with the method.
402 static bool
403 check_method(FILE *fp, gmx_ana_selmethod_t *method,
404 const gmx::SelectionParserSymbolTable &symtab)
406 bool bOk = true;
408 /* Check the type */
409 if (method->type == NO_VALUE)
411 report_error(fp, method->name, "error: no value type specified");
412 bOk = false;
414 if (method->type == STR_VALUE && method->nparams > 0)
416 report_error(fp, method->name, "error: evaluates to a string but is not a keyword");
417 bOk = false;
419 /* Check flags */
420 if (method->type == GROUP_VALUE)
422 /* Group methods should always have SMETH_SINGLEVAL,
423 * so set it for convenience. */
424 method->flags |= SMETH_SINGLEVAL;
425 /* Check that conflicting flags are not present. */
426 if (method->flags & SMETH_VARNUMVAL)
428 report_error(fp, method->name, "error: SMETH_VARNUMVAL cannot be set for group-valued methods");
429 bOk = false;
432 else
434 if ((method->flags & SMETH_SINGLEVAL)
435 && (method->flags & SMETH_VARNUMVAL))
437 report_error(fp, method->name, "error: SMETH_SINGLEVAL and SMETH_VARNUMVAL both set");
438 bOk = false;
441 if ((method->flags & SMETH_CHARVAL) && method->type != STR_VALUE)
443 report_error(fp, method->name, "error: SMETH_CHARVAL can only be specified for STR_VALUE methods");
444 bOk = false;
446 /* Check the parameters */
447 if (!check_params(fp, method->name, method->nparams, method->param, symtab))
449 bOk = false;
451 /* Check the callback pointers */
452 if (!check_callbacks(fp, method))
454 bOk = false;
457 return bOk;
460 /*! \brief
461 * Checks the validity of a selection modifier method.
463 * \param[in] fp File handle to use for diagnostic messages
464 * (can be NULL).
465 * \param[in,out] method Method to check.
466 * \param[in] symtab Symbol table (used for checking overlaps).
468 * Checks the validity of the given selection method data structure
469 * that has \ref SMETH_MODIFIER set.
470 * If you remove a check, please make sure that the selection parser,
471 * compiler, and evaluation functions can deal with the method.
473 static bool
474 check_modifier(FILE *fp, gmx_ana_selmethod_t *method,
475 const gmx::SelectionParserSymbolTable &symtab)
477 bool bOk = true;
479 /* Check the type */
480 if (method->type != NO_VALUE && method->type != POS_VALUE)
482 report_error(fp, method->name, "error: modifier should have type POS_VALUE or NO_VALUE");
483 bOk = false;
485 /* Check flags */
486 if (method->flags & (SMETH_SINGLEVAL | SMETH_VARNUMVAL))
488 report_error(fp, method->name, "error: modifier should not have SMETH_SINGLEVAL or SMETH_VARNUMVAL set");
489 bOk = false;
491 /* Check the parameters */
492 /* The first parameter is skipped */
493 if (!check_params(fp, method->name, method->nparams-1, method->param+1, symtab))
495 bOk = false;
497 /* Check the callback pointers */
498 if (!check_callbacks(fp, method))
500 bOk = false;
502 if (method->update)
504 report_error(fp, method->name, "error: modifier should not have update");
505 bOk = false;
507 if (method->type == POS_VALUE && !method->pupdate)
509 report_error(fp, method->name, "error: evaluation function missing");
510 bOk = false;
513 return bOk;
517 * \param[in,out] symtab Symbol table to register the method to.
518 * \param[in] name Name under which the method should be registered.
519 * \param[in] method Method to register.
520 * \returns 0 on success, -1 if there was something wrong with the
521 * method.
523 * \p name does not need to match the name of the method, and the same
524 * method can be registered multiple times under different names.
525 * If \p name equals some previously registered name,
526 * an error message is printed and the method is not registered.
528 * The function also performs some sanity checking on the input method,
529 * and refuses to register it if there are problems.
530 * Some problems only generate warnings.
531 * All problems are described to \p stderr.
534 gmx_ana_selmethod_register(gmx::SelectionParserSymbolTable *symtab,
535 const char *name, gmx_ana_selmethod_t *method)
537 bool bOk;
539 /* Check the method */
540 if (method->flags & SMETH_MODIFIER)
542 bOk = check_modifier(stderr, method, *symtab);
544 else
546 bOk = check_method(stderr, method, *symtab);
548 /* Try to register the method if everything is ok */
549 if (bOk)
553 symtab->addMethod(name, method);
555 catch (const gmx::APIError &ex)
557 report_error(stderr, name, "%s", ex.what());
558 bOk = false;
561 if (!bOk)
563 report_error(stderr, name, "warning: not registered");
564 return -1;
566 return 0;
570 * \param[in,out] symtab Symbol table to register the methods to.
571 * \returns 0 on success, -1 if any of the default methods could not be
572 * registered.
575 gmx_ana_selmethod_register_defaults(gmx::SelectionParserSymbolTable *symtab)
577 /* Array of selection methods defined in the library. */
578 const t_register_method smtable_def[] = {
579 {nullptr, &sm_cog},
580 {nullptr, &sm_com},
582 {nullptr, &sm_all},
583 {nullptr, &sm_none},
584 {nullptr, &sm_atomnr},
585 {nullptr, &sm_resnr},
586 {"resid", &sm_resnr},
587 {nullptr, &sm_resindex},
588 {"residue", &sm_resindex},
589 {nullptr, &sm_molindex},
590 {"mol", &sm_molindex},
591 {"molecule", &sm_molindex},
592 {nullptr, &sm_atomname},
593 {"name", &sm_atomname},
594 {nullptr, &sm_pdbatomname},
595 {"pdbname", &sm_pdbatomname},
596 {nullptr, &sm_atomtype},
597 {"type", &sm_atomtype},
598 {nullptr, &sm_resname},
599 {nullptr, &sm_insertcode},
600 {nullptr, &sm_chain},
601 {nullptr, &sm_mass},
602 {nullptr, &sm_charge},
603 {nullptr, &sm_altloc},
604 {nullptr, &sm_occupancy},
605 {nullptr, &sm_betafactor},
606 {"beta", &sm_betafactor},
607 {nullptr, &sm_x},
608 {nullptr, &sm_y},
609 {nullptr, &sm_z},
611 {nullptr, &sm_distance},
612 {"dist", &sm_distance},
613 {nullptr, &sm_mindistance},
614 {"mindist", &sm_mindistance},
615 {nullptr, &sm_within},
616 {nullptr, &sm_insolidangle},
617 {nullptr, &sm_same},
619 {nullptr, &sm_merge},
620 {nullptr, &sm_plus},
621 {nullptr, &sm_permute},
624 size_t i;
625 int rc;
626 bool bOk;
628 bOk = true;
629 for (i = 0; i < asize(smtable_def); ++i)
631 gmx_ana_selmethod_t *method = smtable_def[i].method;
633 if (smtable_def[i].name == nullptr)
635 rc = gmx_ana_selmethod_register(symtab, method->name, method);
637 else
639 rc = gmx_ana_selmethod_register(symtab, smtable_def[i].name, method);
641 if (rc != 0)
643 bOk = false;
646 return bOk ? 0 : -1;
650 * \param[in] name Name of the parameter to search.
651 * \param[in] method Method to search for the parameter.
652 * \returns Pointer to the parameter in the
653 * \ref gmx_ana_selmethod_t::param "method->param" array,
654 * or NULL if no parameter with name \p name was found.
656 * This is a simple wrapper for gmx_ana_selparam_find().
658 gmx_ana_selparam_t *
659 gmx_ana_selmethod_find_param(const char *name, gmx_ana_selmethod_t *method)
661 return gmx_ana_selparam_find(name, method->nparams, method->param);