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