Warn for type mismatch for gmx printf like functions 1/3
[alexxy/gromacs.git] / src / gromacs / selection / selmethod.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
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.
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 functions in selmethod.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_selection
41  */
42 #include "gmxpre.h"
43
44 #include "selmethod.h"
45
46 #include <ctype.h>
47 #include <stdarg.h>
48
49 #include "gromacs/utility/arraysize.h"
50 #include "gromacs/utility/cstringutil.h"
51 #include "gromacs/utility/exceptions.h"
52
53 #include "selmethod-impl.h"
54 #include "symrec.h"
55
56 /*! \internal \brief
57  * Helper structure for defining selection methods.
58  */
59 typedef struct {
60     /*! \brief
61      * Name to register the method under.
62      *
63      * If NULL, use the actual name of the method.
64      * This field is used for defining synonyms.
65      */
66     const char            *name;
67     /** Method data structure to register. */
68     gmx_ana_selmethod_t   *method;
69 } t_register_method;
70
71 /** Array of selection methods defined in the library. */
72 static const t_register_method smtable_def[] = {
73     {nullptr,         &sm_cog},
74     {nullptr,         &sm_com},
75
76     {nullptr,         &sm_all},
77     {nullptr,         &sm_none},
78     {nullptr,         &sm_atomnr},
79     {nullptr,         &sm_resnr},
80     {"resid",      &sm_resnr},
81     {nullptr,         &sm_resindex},
82     {"residue",    &sm_resindex},
83     {nullptr,         &sm_molindex},
84     {"mol",        &sm_molindex},
85     {"molecule",   &sm_molindex},
86     {nullptr,         &sm_atomname},
87     {"name",       &sm_atomname},
88     {nullptr,         &sm_pdbatomname},
89     {"pdbname",    &sm_pdbatomname},
90     {nullptr,         &sm_atomtype},
91     {"type",       &sm_atomtype},
92     {nullptr,         &sm_resname},
93     {nullptr,         &sm_insertcode},
94     {nullptr,         &sm_chain},
95     {nullptr,         &sm_mass},
96     {nullptr,         &sm_charge},
97     {nullptr,         &sm_altloc},
98     {nullptr,         &sm_occupancy},
99     {nullptr,         &sm_betafactor},
100     {"beta",       &sm_betafactor},
101     {nullptr,         &sm_x},
102     {nullptr,         &sm_y},
103     {nullptr,         &sm_z},
104
105     {nullptr,         &sm_distance},
106     {"dist",       &sm_distance},
107     {nullptr,         &sm_mindistance},
108     {"mindist",    &sm_mindistance},
109     {nullptr,         &sm_within},
110     {nullptr,         &sm_insolidangle},
111     {nullptr,         &sm_same},
112
113     {nullptr,         &sm_merge},
114     {nullptr,         &sm_plus},
115     {nullptr,         &sm_permute},
116 };
117
118 /*! \brief
119  * Convenience function for reporting errors found in selection methods.
120  */
121 static void
122 report_error(FILE *fp, const char *name, const char *fmt, ...)
123 {
124     va_list ap;
125     va_start(ap, fmt);
126     if (fp)
127     {
128         fprintf(fp, "selection method '%s': ", name);
129         vfprintf(fp, fmt, ap);
130         fprintf(fp, "\n");
131     }
132     va_end(ap);
133 }
134
135 /*! \brief
136  * Convenience function for reporting errors found in selection method parameters.
137  */
138 static void
139 report_param_error(FILE *fp, const char *mname, const char *pname,
140                    const char *fmt, ...)
141 {
142     va_list ap;
143     va_start(ap, fmt);
144     if (fp)
145     {
146         fprintf(fp, "selection method '%s': parameter '%s': ", mname, pname);
147         vfprintf(fp, fmt, ap);
148         fprintf(fp, "\n");
149     }
150     va_end(ap);
151 }
152
153 /*! \brief
154  * Checks the validity of parameters.
155  *
156  * \param[in]     fp      File handle to use for diagnostic messages
157  *   (can be NULL).
158  * \param[in]     name    Name of the method (used for error messages).
159  * \param[in]     nparams Number of parameters in \p param.
160  * \param[in,out] param   Parameter array
161  *   (only the \c flags field of boolean parameters may be modified).
162  * \param[in]     symtab  Symbol table (used for checking overlaps).
163  * \returns       true if there are no problems with the parameters,
164  *   false otherwise.
165  *
166  * This function performs some checks common to both check_method() and
167  * check_modifier().
168  * The purpose of these checks is to ensure that the selection parser does not
169  * need to check for the validity of the parameters at each turn, and to
170  * report programming errors as early as possible.
171  * If you remove a check, make sure that the parameter parser can handle the
172  * resulting parameters.
173  */
174 static bool
175 check_params(FILE *fp, const char *name, int nparams, gmx_ana_selparam_t param[],
176              const gmx::SelectionParserSymbolTable &symtab)
177 {
178     bool              bOk = true;
179     int               i, j;
180
181     if (nparams > 0 && !param)
182     {
183         report_error(fp, name, "error: missing parameter data");
184         return false;
185     }
186     if (nparams == 0 && param)
187     {
188         report_error(fp, name, "warning: parameter data unused because nparams=0");
189     }
190     /* Check each parameter */
191     for (i = 0; i < nparams; ++i)
192     {
193         /* Check that there is at most one NULL name, in the beginning */
194         if (param[i].name == nullptr && i > 0)
195         {
196             report_error(fp, name, "error: NULL parameter should be the first one");
197             bOk = false;
198             continue;
199         }
200         /* Check for duplicates */
201         for (j = 0; j < i; ++j)
202         {
203             if (param[j].name == nullptr)
204             {
205                 continue;
206             }
207             if (!gmx_strcasecmp(param[i].name, param[j].name))
208             {
209                 report_error(fp, name, "error: duplicate parameter name '%s'", param[i].name);
210                 bOk = false;
211                 break;
212             }
213         }
214         /* Check flags */
215         if (param[i].flags & SPAR_SET)
216         {
217             report_param_error(fp, name, param[i].name, "warning: flag SPAR_SET is set");
218             param[i].flags &= ~SPAR_SET;
219         }
220         if (param[i].flags & SPAR_RANGES)
221         {
222             if (param[i].val.type != INT_VALUE && param[i].val.type != REAL_VALUE)
223             {
224                 report_param_error(fp, name, param[i].name, "error: SPAR_RANGES cannot be set for a non-numeric parameter");
225                 bOk = false;
226             }
227             if (param[i].flags & SPAR_DYNAMIC)
228             {
229                 report_param_error(fp, name, param[i].name, "warning: SPAR_DYNAMIC does not have effect with SPAR_RANGES");
230                 param[i].flags &= ~SPAR_DYNAMIC;
231             }
232             if (!(param[i].flags & SPAR_VARNUM) && param[i].val.nr != 1)
233             {
234                 report_param_error(fp, name, param[i].name, "error: range should take either one or an arbitrary number of values");
235                 bOk = false;
236             }
237             if (param[i].flags & SPAR_ATOMVAL)
238             {
239                 report_param_error(fp, name, param[i].name, "error: SPAR_RANGES and SPAR_ATOMVAL both set");
240                 bOk = false;
241             }
242         }
243         if ((param[i].flags & SPAR_VARNUM) && (param[i].flags & SPAR_ATOMVAL))
244         {
245             report_param_error(fp, name, param[i].name, "error: SPAR_VARNUM and SPAR_ATOMVAL both set");
246             bOk = false;
247         }
248         if (param[i].flags & SPAR_ENUMVAL)
249         {
250             if (param[i].val.type != STR_VALUE)
251             {
252                 report_param_error(fp, name, param[i].name, "error: SPAR_ENUMVAL can only be set for string parameters");
253                 bOk = false;
254             }
255             if (param[i].val.nr != 1)
256             {
257                 report_param_error(fp, name, param[i].name, "error: SPAR_ENUMVAL parameters should take exactly one value");
258                 bOk = false;
259             }
260             if (param[i].flags & (SPAR_DYNAMIC | SPAR_VARNUM | SPAR_ATOMVAL))
261             {
262                 report_param_error(fp, name, param[i].name, "error: only SPAR_OPTIONAL supported with SPAR_ENUMVAL");
263                 bOk = false;
264             }
265         }
266         /* Check boolean parameters */
267         if (param[i].val.type == NO_VALUE)
268         {
269             if (param[i].val.nr != 0)
270             {
271                 report_param_error(fp, name, param[i].name, "error: number of values should be zero for boolean parameters");
272                 bOk = false;
273             }
274             /* The boolean parameters should always be optional, so set the
275              * flag for convenience. */
276             param[i].flags |= SPAR_OPTIONAL;
277             /* Any other flags should not be specified */
278             if (param[i].flags & ~SPAR_OPTIONAL)
279             {
280                 report_param_error(fp, name, param[i].name, "error: boolean parameter should not have any flags set");
281                 bOk = false;
282             }
283         }
284         /* Check val.nr */
285         if (param[i].flags & (SPAR_VARNUM | SPAR_ATOMVAL))
286         {
287             if (param[i].val.nr != -1)
288             {
289                 report_param_error(fp, name, param[i].name, "warning: val.nr is not -1 although SPAR_VARNUM/SPAR_ATOMVAL is set");
290             }
291             param[i].val.nr = -1;
292         }
293         else if (param[i].val.type != NO_VALUE)
294         {
295             if (param[i].val.nr <= 0)
296             {
297                 report_param_error(fp, name, param[i].name, "error: val.nr <= 0");
298                 bOk = false;
299             }
300         }
301         /* Check that the value pointer is NULL */
302         if (param[i].nvalptr != nullptr)
303         {
304             report_param_error(fp, name, param[i].name, "warning: nvalptr is set");
305         }
306         if (param[i].val.u.ptr != nullptr && !(param[i].flags & SPAR_ENUMVAL))
307         {
308             report_param_error(fp, name, param[i].name, "warning: value pointer is set");
309         }
310         /* Check that the name contains only valid characters */
311         if (param[i].name == nullptr)
312         {
313             continue;
314         }
315         if (!isalpha(param[i].name[0]))
316         {
317             report_param_error(fp, name, param[i].name, "error: name does not begin with a letter");
318             bOk = false;
319             continue;
320         }
321         for (j = 1; param[i].name[j] != 0; ++j)
322         {
323             if (param[i].name[j] != '_' && !isalnum(param[i].name[j]))
324             {
325                 report_param_error(fp, name, param[i].name, "error: name contains non-alphanumeric characters");
326                 bOk = false;
327                 break;
328             }
329         }
330         if (param[i].name[j] != 0)
331         {
332             continue;
333         }
334         /* Check that the name does not conflict with a method */
335         if (symtab.findSymbol(param[i].name))
336         {
337             report_param_error(fp, name, param[i].name, "error: name conflicts with another method or a keyword");
338             bOk = false;
339         }
340     } /* End of parameter loop */
341       /* Check parameters of existing methods */
342     gmx::SelectionParserSymbolIterator symbol
343         = symtab.beginIterator(gmx::SelectionParserSymbol::MethodSymbol);
344     while (symbol != symtab.endIterator())
345     {
346         gmx_ana_selmethod_t *method = symbol->methodValue();
347         gmx_ana_selparam_t  *param  =
348             gmx_ana_selmethod_find_param(name, method);
349         if (param)
350         {
351             report_param_error(fp, method->name, param->name, "error: name conflicts with another method or a keyword");
352             bOk = false;
353         }
354         ++symbol;
355     }
356     return bOk;
357 }
358
359 /*! \brief
360  * Checks the validity of selection method callback functions.
361  *
362  * \param[in] fp        File handle to use for diagnostic messages
363  *   (can be NULL).
364  * \param[in] method    The method to check.
365  * \returns   true if there are no problems, false otherwise.
366  *
367  * This function performs some checks common to both check_method() and
368  * check_modifier().
369  * This function checks that all the required callbacks are defined, i.e.,
370  * not NULL, to find programming errors.
371  */
372 static bool
373 check_callbacks(FILE *fp, gmx_ana_selmethod_t *method)
374 {
375     bool         bOk = true;
376     bool         bNeedInit;
377     int          i;
378
379     /* Make some checks on init_data and free */
380     if (method->nparams > 0 && !method->init_data)
381     {
382         report_error(fp, method->name, "error: init_data should be provided because the method has parameters");
383         bOk = false;
384     }
385     if (method->free && !method->init_data)
386     {
387         report_error(fp, method->name, "warning: free is not used because of missing init_data");
388     }
389     /* Check presence of outinit for position-valued methods */
390     if (method->type == POS_VALUE && !method->outinit)
391     {
392         report_error(fp, method->name, "error: outinit should be provided because the method has POS_VALUE");
393         bOk = false;
394     }
395     /* Check presence of outinit for variable output count methods */
396     if ((method->flags & SMETH_VARNUMVAL) && !method->outinit)
397     {
398         report_error(fp, method->name, "error: outinit should be provided because the method has SMETH_VARNUMVAL");
399         bOk = false;
400     }
401     /* Warn of dynamic callbacks in static methods */
402     if (!(method->flags & SMETH_MODIFIER))
403     {
404         if (method->pupdate && !(method->flags & SMETH_DYNAMIC))
405         {
406             report_error(fp, method->name, "warning: pupdate not used because the method is static");
407             method->pupdate = nullptr;
408         }
409     }
410     /* Check that there is an evaluation function */
411     if (method->type != NO_VALUE && !method->update && !method->pupdate)
412     {
413         report_error(fp, method->name, "error: evaluation function missing");
414         bOk = false;
415     }
416     /* Loop through the parameters to determine if initialization callbacks
417      * are needed. */
418     bNeedInit = false;
419     for (i = 0; i < method->nparams; ++i)
420     {
421         if (method->param[i].val.type != POS_VALUE
422             && (method->param[i].flags & (SPAR_VARNUM | SPAR_ATOMVAL)))
423         {
424             bNeedInit = true;
425         }
426     }
427     /* Check that the callbacks required by the parameters are present */
428     if (bNeedInit && !method->init)
429     {
430         report_error(fp, method->name, "error: init should be provided");
431         bOk = false;
432     }
433     return bOk;
434 }
435
436 /*! \brief
437  * Checks the validity of a selection method.
438  *
439  * \param[in]     fp     File handle to use for diagnostic messages
440  *   (can be NULL).
441  * \param[in,out] method Method to check.
442  * \param[in]     symtab Symbol table (used for checking overlaps).
443  *
444  * Checks the validity of the given selection method data structure
445  * that does not have \ref SMETH_MODIFIER set.
446  * If you remove a check, please make sure that the selection parser,
447  * compiler, and evaluation functions can deal with the method.
448  */
449 static bool
450 check_method(FILE *fp, gmx_ana_selmethod_t *method,
451              const gmx::SelectionParserSymbolTable &symtab)
452 {
453     bool         bOk = true;
454
455     /* Check the type */
456     if (method->type == NO_VALUE)
457     {
458         report_error(fp, method->name, "error: no value type specified");
459         bOk = false;
460     }
461     if (method->type == STR_VALUE && method->nparams > 0)
462     {
463         report_error(fp, method->name, "error: evaluates to a string but is not a keyword");
464         bOk = false;
465     }
466     /* Check flags */
467     if (method->type == GROUP_VALUE)
468     {
469         /* Group methods should always have SMETH_SINGLEVAL,
470          * so set it for convenience. */
471         method->flags |= SMETH_SINGLEVAL;
472         /* Check that conflicting flags are not present. */
473         if (method->flags & SMETH_VARNUMVAL)
474         {
475             report_error(fp, method->name, "error: SMETH_VARNUMVAL cannot be set for group-valued methods");
476             bOk = false;
477         }
478     }
479     else
480     {
481         if ((method->flags & SMETH_SINGLEVAL)
482             && (method->flags & SMETH_VARNUMVAL))
483         {
484             report_error(fp, method->name, "error: SMETH_SINGLEVAL and SMETH_VARNUMVAL both set");
485             bOk = false;
486         }
487     }
488     if ((method->flags & SMETH_CHARVAL) && method->type != STR_VALUE)
489     {
490         report_error(fp, method->name, "error: SMETH_CHARVAL can only be specified for STR_VALUE methods");
491         bOk = false;
492     }
493     /* Check the parameters */
494     if (!check_params(fp, method->name, method->nparams, method->param, symtab))
495     {
496         bOk = false;
497     }
498     /* Check the callback pointers */
499     if (!check_callbacks(fp, method))
500     {
501         bOk = false;
502     }
503
504     return bOk;
505 }
506
507 /*! \brief
508  * Checks the validity of a selection modifier method.
509  *
510  * \param[in]     fp     File handle to use for diagnostic messages
511  *   (can be NULL).
512  * \param[in,out] method Method to check.
513  * \param[in]     symtab Symbol table (used for checking overlaps).
514  *
515  * Checks the validity of the given selection method data structure
516  * that has \ref SMETH_MODIFIER set.
517  * If you remove a check, please make sure that the selection parser,
518  * compiler, and evaluation functions can deal with the method.
519  */
520 static bool
521 check_modifier(FILE *fp, gmx_ana_selmethod_t *method,
522                const gmx::SelectionParserSymbolTable &symtab)
523 {
524     bool         bOk = true;
525
526     /* Check the type */
527     if (method->type != NO_VALUE && method->type != POS_VALUE)
528     {
529         report_error(fp, method->name, "error: modifier should have type POS_VALUE or NO_VALUE");
530         bOk = false;
531     }
532     /* Check flags */
533     if (method->flags & (SMETH_SINGLEVAL | SMETH_VARNUMVAL))
534     {
535         report_error(fp, method->name, "error: modifier should not have SMETH_SINGLEVAL or SMETH_VARNUMVAL set");
536         bOk = false;
537     }
538     /* Check the parameters */
539     /* The first parameter is skipped */
540     if (!check_params(fp, method->name, method->nparams-1, method->param+1, symtab))
541     {
542         bOk = false;
543     }
544     /* Check the callback pointers */
545     if (!check_callbacks(fp, method))
546     {
547         bOk = false;
548     }
549     if (method->update)
550     {
551         report_error(fp, method->name, "error: modifier should not have update");
552         bOk = false;
553     }
554     if (method->type == POS_VALUE && !method->pupdate)
555     {
556         report_error(fp, method->name, "error: evaluation function missing");
557         bOk = false;
558     }
559
560     return bOk;
561 }
562
563 /*!
564  * \param[in,out] symtab Symbol table to register the method to.
565  * \param[in]     name   Name under which the method should be registered.
566  * \param[in]     method Method to register.
567  * \returns       0 on success, -1 if there was something wrong with the
568  *   method.
569  *
570  * \p name does not need to match the name of the method, and the same
571  * method can be registered multiple times under different names.
572  * If \p name equals some previously registered name,
573  * an error message is printed and the method is not registered.
574  *
575  * The function also performs some sanity checking on the input method,
576  * and refuses to register it if there are problems.
577  * Some problems only generate warnings.
578  * All problems are described to \p stderr.
579  */
580 int
581 gmx_ana_selmethod_register(gmx::SelectionParserSymbolTable *symtab,
582                            const char *name, gmx_ana_selmethod_t *method)
583 {
584     bool bOk;
585
586     /* Check the method */
587     if (method->flags & SMETH_MODIFIER)
588     {
589         bOk = check_modifier(stderr, method, *symtab);
590     }
591     else
592     {
593         bOk = check_method(stderr, method, *symtab);
594     }
595     /* Try to register the method if everything is ok */
596     if (bOk)
597     {
598         try
599         {
600             symtab->addMethod(name, method);
601         }
602         catch (const gmx::APIError &ex)
603         {
604             report_error(stderr, name, "%s", ex.what());
605             bOk = false;
606         }
607     }
608     if (!bOk)
609     {
610         report_error(stderr, name, "warning: not registered");
611         return -1;
612     }
613     return 0;
614 }
615
616 /*!
617  * \param[in,out] symtab Symbol table to register the methods to.
618  * \returns       0 on success, -1 if any of the default methods could not be
619  *   registered.
620  */
621 int
622 gmx_ana_selmethod_register_defaults(gmx::SelectionParserSymbolTable *symtab)
623 {
624     size_t i;
625     int    rc;
626     bool   bOk;
627
628     bOk = true;
629     for (i = 0; i < asize(smtable_def); ++i)
630     {
631         gmx_ana_selmethod_t *method = smtable_def[i].method;
632
633         if (smtable_def[i].name == nullptr)
634         {
635             rc = gmx_ana_selmethod_register(symtab, method->name, method);
636         }
637         else
638         {
639             rc = gmx_ana_selmethod_register(symtab, smtable_def[i].name, method);
640         }
641         if (rc != 0)
642         {
643             bOk = false;
644         }
645     }
646     return bOk ? 0 : -1;
647 }
648
649 /*!
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.
655  *
656  * This is a simple wrapper for gmx_ana_selparam_find().
657  */
658 gmx_ana_selparam_t *
659 gmx_ana_selmethod_find_param(const char *name, gmx_ana_selmethod_t *method)
660 {
661     return gmx_ana_selparam_find(name, method->nparams, method->param);
662 }