3/3 of old-style casting
[alexxy/gromacs.git] / src / gromacs / fileio / readinp.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2004, The GROMACS development team.
6  * Copyright (c) 2013,2014,2015,2016,2017,2018, by the GROMACS development team, led by
7  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8  * and including many others, as listed in the AUTHORS file in the
9  * top-level source directory and at http://www.gromacs.org.
10  *
11  * GROMACS is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public License
13  * as published by the Free Software Foundation; either version 2.1
14  * of the License, or (at your option) any later version.
15  *
16  * GROMACS is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with GROMACS; if not, see
23  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
25  *
26  * If you want to redistribute modifications to GROMACS, please
27  * consider that scientific software is very special. Version
28  * control is crucial - bugs must be traceable. We will be happy to
29  * consider code for inclusion in the official distribution, but
30  * derived work must not be called official GROMACS. Details are found
31  * in the README & COPYING files - if they are missing, get the
32  * official version at http://www.gromacs.org.
33  *
34  * To help us fund GROMACS development, we humbly ask that you cite
35  * the research papers on the package. Check out http://www.gromacs.org.
36  */
37 #include "gmxpre.h"
38
39 #include "readinp.h"
40
41 #include <cstdlib>
42 #include <cstring>
43
44 #include <algorithm>
45
46 #include "gromacs/fileio/warninp.h"
47 #include "gromacs/utility/binaryinformation.h"
48 #include "gromacs/utility/cstringutil.h"
49 #include "gromacs/utility/exceptions.h"
50 #include "gromacs/utility/fatalerror.h"
51 #include "gromacs/utility/keyvaluetreebuilder.h"
52 #include "gromacs/utility/niceheader.h"
53 #include "gromacs/utility/programcontext.h"
54 #include "gromacs/utility/smalloc.h"
55 #include "gromacs/utility/stringutil.h"
56 #include "gromacs/utility/textreader.h"
57 #include "gromacs/utility/textwriter.h"
58
59 std::vector<t_inpfile>
60 read_inpfile(gmx::TextInputStream *stream, const char *fn,
61              warninp_t wi)
62 {
63     std::vector<t_inpfile> inp;
64
65     if (debug)
66     {
67         fprintf(debug, "Reading MDP file %s\n", fn);
68     }
69
70     int             indexOfLineReadFromFile = 0;
71     std::string     line;
72     gmx::TextReader reader(stream);
73     reader.setTrimTrailingWhiteSpace(true);
74     reader.setTrimTrailingComment(true, ';');
75     while (reader.readLine(&line))
76     {
77         indexOfLineReadFromFile++;
78         set_warning_line(wi, fn, indexOfLineReadFromFile);
79
80         if (line.empty())
81         {
82             continue;
83         }
84
85         auto tokens = gmx::splitAndTrimDelimitedString(line, '=');
86         if (tokens.size() < 2)
87         {
88             // TODO this seems like it silently ignores the user accidentally deleting an equals sign...
89             if (debug)
90             {
91                 fprintf(debug, "No = on line %d in file %s, ignored\n", indexOfLineReadFromFile, fn);
92             }
93             continue;
94         }
95         if (tokens.size() > 2)
96         {
97             // More than one equals symbol in the original line is
98             // valid if the RHS is a free string, and needed for
99             // "define = -DBOOLVAR -DVAR=VALUE".
100             //
101             // First, drop all the fields on the RHS of the first equals symbol.
102             tokens.resize(1);
103             // This find cannot return std::string::npos.
104             auto firstEqualsPos = line.find('=');
105             tokens.emplace_back(gmx::stripString(line.substr(firstEqualsPos + 1)));
106         }
107         if (tokens[0].empty())
108         {
109             // TODO ignoring such lines does not seem like good behaviour
110             if (debug)
111             {
112                 fprintf(debug, "Empty left hand side on line %d in file %s, ignored\n", indexOfLineReadFromFile, fn);
113             }
114             continue;
115         }
116         if (tokens[1].empty())
117         {
118             // TODO ignoring such lines does not seem like good behaviour
119             if (debug)
120             {
121                 fprintf(debug, "Empty right hand side on line %d in file %s, ignored\n", indexOfLineReadFromFile, fn);
122             }
123             continue;
124         }
125
126         /* Now finally something sensible; check for duplicates */
127         int found_index = search_einp(inp, tokens[0].c_str());
128
129         if (found_index == -1)
130         {
131             /* add a new item */
132             inp.emplace_back(0, 1, false, false, false,
133                              tokens[0], tokens[1]);
134         }
135         else
136         {
137             auto message = gmx::formatString("Parameter \"%s\" doubly defined\n",
138                                              tokens[0].c_str());
139             warning_error(wi, message.c_str());
140         }
141     }
142     /* This preserves the behaviour of the old code, which issues some
143        warnings after completing parsing. Regenerating regressiontest
144        warning files is not worth the effort. */
145     indexOfLineReadFromFile++;
146     set_warning_line(wi, fn, indexOfLineReadFromFile);
147
148     if (debug)
149     {
150         fprintf(debug, "Done reading MDP file, there were %zu entries in there\n",
151                 inp.size());
152     }
153
154     return inp;
155 }
156
157 gmx::KeyValueTreeObject flatKeyValueTreeFromInpFile(gmx::ArrayRef<const t_inpfile> inp)
158 {
159     gmx::KeyValueTreeBuilder  builder;
160     auto                      root = builder.rootObject();
161     for (auto &local : inp)
162     {
163         root.addValue<std::string>(local.name_, !local.value_.empty() ? local.value_ : "");
164     }
165     return builder.build();
166 }
167
168
169 struct inp_comp
170 {
171     bool operator()(t_inpfile const &a, t_inpfile const &b)
172     {
173         return a.count_ < b.count_;
174     }
175 };
176
177 static void sort_inp(std::vector<t_inpfile> *inp)
178 {
179     std::vector<t_inpfile> &inpRef = *inp;
180     int                     mm;
181
182     mm = -1;
183     for (const auto &local : inpRef)
184     {
185         mm = std::max(mm, local.count_);
186     }
187     for (auto &local : inpRef)
188     {
189         if (local.count_ == 0)
190         {
191             local.count_ = mm++;
192         }
193     }
194     std::sort(inpRef.begin(), inpRef.end(), inp_comp());
195 }
196
197 void write_inpfile(gmx::TextOutputStream *stream, const char *fn, std::vector<t_inpfile> *inp,
198                    gmx_bool bHaltOnUnknown,
199                    WriteMdpHeader writeHeader,
200                    warninp_t wi)
201 {
202     using gmx::formatString;
203
204     sort_inp(inp);
205
206     gmx::TextWriter writer(stream);
207     if (writeHeader == WriteMdpHeader::yes)
208     {
209         gmx::niceHeader(&writer, fn, ';');
210
211         gmx::BinaryInformationSettings settings;
212         settings.generatedByHeader(true);
213         settings.linePrefix(";\t");
214         gmx::printBinaryInformation(&writer, gmx::getProgramContext(), settings);
215     }
216     for (const auto &local : *inp)
217     {
218         if (local.bHandledAsKeyValueTree_)
219         {
220         }
221         else if (local.bSet_)
222         {
223             if (local.name_[0] == ';' || (local.name_.length() > 2 && local.name_[1] == ';'))
224             {
225                 writer.writeLine(formatString("%-24s", local.name_.c_str()));
226             }
227             else
228             {
229                 writer.writeLine(formatString("%-24s = %s", local.name_.c_str(), !local.value_.empty() ? local.value_.c_str() : ""));
230             }
231         }
232         else if (!local.bObsolete_)
233         {
234             auto message = formatString("Unknown left-hand '%s' in parameter file\n",
235                                         local.name_.c_str());
236             if (bHaltOnUnknown)
237             {
238                 warning_error(wi, message.c_str());
239             }
240             else
241             {
242                 warning(wi, message.c_str());
243             }
244         }
245     }
246
247     check_warning_error(wi, FARGS);
248 }
249
250 void replace_inp_entry(gmx::ArrayRef<t_inpfile> inp, const char *old_entry, const char *new_entry)
251 {
252     for (auto &local : inp)
253     {
254         if (gmx_strcasecmp_min(old_entry, local.name_.c_str()) == 0)
255         {
256             if (new_entry)
257             {
258                 fprintf(stderr, "Replacing old mdp entry '%s' by '%s'\n",
259                         local.name_.c_str(), new_entry);
260
261                 int foundIndex = search_einp(inp, new_entry);
262                 if (foundIndex >= 0)
263                 {
264                     gmx_fatal(FARGS, "A parameter is present with both the old name '%s' and the new name '%s'.", local.name_.c_str(), inp[foundIndex].name_.c_str());
265                 }
266
267                 local.name_.assign(new_entry);
268             }
269             else
270             {
271                 fprintf(stderr, "Ignoring obsolete mdp entry '%s'\n",
272                         local.name_.c_str());
273                 local.bObsolete_ = TRUE;
274             }
275         }
276     }
277 }
278
279 int search_einp(gmx::ArrayRef<const t_inpfile> inp, const char *name)
280 {
281     if (inp.empty())
282     {
283         return -1;
284     }
285     for (gmx::index i = 0; i < inp.size(); i++)
286     {
287         if (gmx_strcasecmp_min(name, inp[i].name_.c_str()) == 0)
288         {
289             return i;
290         }
291     }
292     return -1;
293 }
294
295 void mark_einp_set(gmx::ArrayRef<t_inpfile> inp, const char *name)
296 {
297     int                     i      = search_einp(inp, name);
298     if (i != -1)
299     {
300         inp[i].count_ = inp.front().inp_count_++;
301         inp[i].bSet_  = TRUE;
302         /* Prevent mdp lines being written twice for
303            options that are handled via key-value trees. */
304         inp[i].bHandledAsKeyValueTree_ = TRUE;
305     }
306 }
307
308 static int get_einp(std::vector<t_inpfile> *inp, const char *name)
309 {
310     std::vector<t_inpfile> &inpRef   = *inp;
311     bool                    notfound = false;
312
313     int                     i = search_einp(inpRef, name);
314
315     if (i == -1)
316     {
317         notfound = true;
318         inpRef.emplace_back(0, 0, false, true, false,
319                             name, "");
320         i = inpRef.size() - 1;
321         if (inpRef.size()  == 1)
322         {
323             inpRef.front().inp_count_ = 1;
324         }
325     }
326     inpRef[i].count_ = inpRef.front().inp_count_++;
327     inpRef[i].bSet_  = TRUE;
328     if (debug)
329     {
330         fprintf(debug, "Inp %d = %s\n", inpRef[i].count_, inpRef[i].name_.c_str());
331     }
332
333     if (notfound)
334     {
335         return -1;
336     }
337     else
338     {
339         return i;
340     }
341 }
342
343 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
344 int get_eint(std::vector<t_inpfile> *inp, const char *name, int def,
345              warninp_t wi)
346 {
347     std::vector<t_inpfile> &inpRef = *inp;
348     char                    buf[32], *ptr, warn_buf[STRLEN];
349
350     int                     ii = get_einp(inp, name);
351
352     if (ii == -1)
353     {
354         sprintf(buf, "%d", def);
355         inpRef.back().value_.assign(buf);
356
357         return def;
358     }
359     else
360     {
361         int ret = std::strtol(inpRef[ii].value_.c_str(), &ptr, 10);
362         if (*ptr != '\0')
363         {
364             sprintf(warn_buf, "Right hand side '%s' for parameter '%s' in parameter file is not an integer value\n", inpRef[ii].value_.c_str(), inpRef[ii].name_.c_str());
365             warning_error(wi, warn_buf);
366         }
367
368         return ret;
369     }
370 }
371
372 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
373 int64_t get_eint64(std::vector<t_inpfile> *inp,
374                    const char *name, int64_t def,
375                    warninp_t wi)
376 {
377     std::vector<t_inpfile> &inpRef = *inp;
378     char                    buf[32], *ptr, warn_buf[STRLEN];
379
380     int                     ii = get_einp(inp, name);
381
382     if (ii == -1)
383     {
384         sprintf(buf, "%" PRId64, def);
385         inpRef.back().value_.assign(buf);
386
387         return def;
388     }
389     else
390     {
391         int64_t ret = str_to_int64_t(inpRef[ii].value_.c_str(), &ptr);
392         if (*ptr != '\0')
393         {
394             sprintf(warn_buf, "Right hand side '%s' for parameter '%s' in parameter file is not an integer value\n", inpRef[ii].value_.c_str(), inpRef[ii].name_.c_str());
395             warning_error(wi, warn_buf);
396         }
397
398         return ret;
399     }
400 }
401
402 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
403 double get_ereal(std::vector<t_inpfile> *inp, const char *name, double def,
404                  warninp_t wi)
405 {
406     std::vector<t_inpfile> &inpRef = *inp;
407     char                    buf[32], *ptr, warn_buf[STRLEN];
408
409     int                     ii = get_einp(inp, name);
410
411     if (ii == -1)
412     {
413         sprintf(buf, "%g", def);
414         inpRef.back().value_.assign(buf);
415
416         return def;
417     }
418     else
419     {
420         double ret = strtod(inpRef[ii].value_.c_str(), &ptr);
421         if (*ptr != '\0')
422         {
423             sprintf(warn_buf, "Right hand side '%s' for parameter '%s' in parameter file is not a real value\n", inpRef[ii].value_.c_str(), inpRef[ii].name_.c_str());
424             warning_error(wi, warn_buf);
425         }
426
427         return ret;
428     }
429 }
430
431 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
432 const char *get_estr(std::vector<t_inpfile> *inp, const char *name, const char *def)
433 {
434     std::vector<t_inpfile> &inpRef = *inp;
435     char                    buf[32];
436
437     int                     ii = get_einp(inp, name);
438
439     if (ii == -1)
440     {
441         if (def)
442         {
443             sprintf(buf, "%s", def);
444             inpRef.back().value_.assign(buf);
445         }
446         else
447         {
448             inpRef.back().value_.clear();
449         }
450
451         return def;
452     }
453     else
454     {
455         return inpRef[ii].value_.c_str();
456     }
457 }
458
459 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
460 int get_eeenum(std::vector<t_inpfile> *inp, const char *name, const char **defs,
461                warninp_t wi)
462 {
463     std::vector<t_inpfile> &inpRef = *inp;
464     int                     n      = 0;
465     char                    buf[STRLEN];
466
467     int                     ii = get_einp(inp, name);
468
469     if (ii == -1)
470     {
471         inpRef.back().value_.assign(defs[0]);
472
473         return 0;
474     }
475     int i = 0;
476     for (i = 0; (defs[i] != nullptr); i++)
477     {
478         if (gmx_strcasecmp_min(defs[i], inpRef[ii].value_.c_str()) == 0)
479         {
480             break;
481         }
482     }
483
484     if (defs[i] == nullptr)
485     {
486         n += sprintf(buf, "Invalid enum '%s' for variable %s, using '%s'\n",
487                      inpRef[ii].value_.c_str(), name, defs[0]);
488         n += sprintf(buf+n, "Next time use one of:");
489         int j  = 0;
490         while (defs[j])
491         {
492             n += sprintf(buf+n, " '%s'", defs[j]);
493             j++;
494         }
495         if (wi != nullptr)
496         {
497             warning_error(wi, buf);
498         }
499         else
500         {
501             fprintf(stderr, "%s\n", buf);
502         }
503
504         inpRef[ii].value_ = gmx_strdup(defs[0]);
505
506         return 0;
507     }
508
509     return i;
510 }
511
512 int get_eenum(std::vector<t_inpfile> *inp, const char *name, const char **defs)
513 {
514     return get_eeenum(inp, name, defs, nullptr);
515 }
516
517 void
518 printStringNewline(std::vector<t_inpfile> *inp, const char *line)
519 {
520     std::string tmp("\n; ");
521     tmp.append(line);
522     get_estr(inp, tmp.c_str(), nullptr);
523 }
524
525 void
526 printStringNoNewline(std::vector<t_inpfile> *inp, const char *line)
527 {
528     std::string tmp("; ");
529     tmp.append(line);
530     get_estr(inp, tmp.c_str(), nullptr);
531 }
532 void
533 setStringEntry(std::vector<t_inpfile> *inp, const char *name, char *newName, const char *def)
534 {
535     const char *found = nullptr;
536     found = get_estr(inp, name, def);
537     if (found != nullptr)
538     {
539         std::strcpy(newName, found);
540     }
541 }