Improved usability of grompp by rejecting more invalid input
[gromacs.git] / src / gromacs / fileio / readinp.cpp
blob743fd325555f0fc85aa53ab57a4a961d99e37e0b
1 /*
2 * This file is part of the GROMACS molecular simulation package.
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,2019, 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.
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.
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.
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.
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.
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.
37 #include "gmxpre.h"
39 #include "readinp.h"
41 #include <cstdlib>
42 #include <cstring>
44 #include <algorithm>
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"
59 std::vector<t_inpfile>
60 read_inpfile(gmx::TextInputStream *stream, const char *fn,
61 warninp_t wi)
63 std::vector<t_inpfile> inp;
65 if (debug)
67 fprintf(debug, "Reading MDP file %s\n", fn);
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))
77 indexOfLineReadFromFile++;
78 set_warning_line(wi, fn, indexOfLineReadFromFile);
80 if (line.empty())
82 continue;
85 auto tokens = gmx::splitAndTrimDelimitedString(line, '=');
86 if (tokens.size() < 2)
88 auto message = gmx::formatString("No '=' to separate .mdp parameter key and value was found on line:\n'%s'", line.c_str());
89 warning_error(wi, message);
90 continue;
92 if (tokens.size() > 2)
94 // More than one equals symbol in the original line is
95 // valid if the RHS is a free string, and needed for
96 // "define = -DBOOLVAR -DVAR=VALUE".
98 // First, drop all the fields on the RHS of the first equals symbol.
99 tokens.resize(1);
100 // This find cannot return std::string::npos.
101 auto firstEqualsPos = line.find('=');
102 tokens.emplace_back(gmx::stripString(line.substr(firstEqualsPos + 1)));
104 GMX_RELEASE_ASSERT(tokens.size() == 2, "Must have tokens for key and value");
105 if (tokens[0].empty() && tokens[1].empty())
107 auto message = gmx::formatString("No .mdp parameter name or value was found on line:\n'%s'", line.c_str());
108 warning_error(wi, message);
109 continue;
111 if (tokens[0].empty())
113 auto message = gmx::formatString("No .mdp parameter name was found on the left-hand side of '=' on line:\n'%s'", line.c_str());
114 warning_error(wi, message);
115 continue;
117 if (tokens[1].empty())
119 // Users are probably using this for lines like
120 // tcoupl = ;v-rescale
121 // comm-grps =
122 // so we accept their intent to use the default behavior.
123 continue;
126 /* Now finally something sensible; check for duplicates */
127 int found_index = search_einp(inp, tokens[0].c_str());
129 if (found_index == -1)
131 /* add a new item */
132 inp.emplace_back(0, 1, false, false, false,
133 tokens[0], tokens[1]);
135 else
137 auto message = gmx::formatString("Parameter \"%s\" doubly defined\n",
138 tokens[0].c_str());
139 warning_error(wi, message);
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);
148 if (debug)
150 fprintf(debug, "Done reading MDP file, there were %zu entries in there\n",
151 inp.size());
154 return inp;
157 gmx::KeyValueTreeObject flatKeyValueTreeFromInpFile(gmx::ArrayRef<const t_inpfile> inp)
159 gmx::KeyValueTreeBuilder builder;
160 auto root = builder.rootObject();
161 for (auto &local : inp)
163 root.addValue<std::string>(local.name_, !local.value_.empty() ? local.value_ : "");
165 return builder.build();
169 struct inp_comp
171 bool operator()(t_inpfile const &a, t_inpfile const &b)
173 return a.count_ < b.count_;
177 static void sort_inp(std::vector<t_inpfile> *inp)
179 std::vector<t_inpfile> &inpRef = *inp;
180 int mm;
182 mm = -1;
183 for (const auto &local : inpRef)
185 mm = std::max(mm, local.count_);
187 for (auto &local : inpRef)
189 if (local.count_ == 0)
191 local.count_ = mm++;
194 std::sort(inpRef.begin(), inpRef.end(), inp_comp());
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)
202 using gmx::formatString;
204 sort_inp(inp);
206 gmx::TextWriter writer(stream);
207 if (writeHeader == WriteMdpHeader::yes)
209 gmx::niceHeader(&writer, fn, ';');
211 gmx::BinaryInformationSettings settings;
212 settings.generatedByHeader(true);
213 settings.linePrefix(";\t");
214 gmx::printBinaryInformation(&writer, gmx::getProgramContext(), settings);
216 for (const auto &local : *inp)
218 if (local.bHandledAsKeyValueTree_)
221 else if (local.bSet_)
223 if (local.name_[0] == ';' || (local.name_.length() > 2 && local.name_[1] == ';'))
225 writer.writeLine(formatString("%-24s", local.name_.c_str()));
227 else
229 writer.writeLine(formatString("%-24s = %s", local.name_.c_str(), !local.value_.empty() ? local.value_.c_str() : ""));
232 else if (!local.bObsolete_)
234 auto message = formatString("Unknown left-hand '%s' in parameter file\n",
235 local.name_.c_str());
236 if (bHaltOnUnknown)
238 warning_error(wi, message.c_str());
240 else
242 warning(wi, message.c_str());
247 check_warning_error(wi, FARGS);
250 void replace_inp_entry(gmx::ArrayRef<t_inpfile> inp, const char *old_entry, const char *new_entry)
252 for (auto &local : inp)
254 if (gmx_strcasecmp_min(old_entry, local.name_.c_str()) == 0)
256 if (new_entry)
258 fprintf(stderr, "Replacing old mdp entry '%s' by '%s'\n",
259 local.name_.c_str(), new_entry);
261 int foundIndex = search_einp(inp, new_entry);
262 if (foundIndex >= 0)
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());
267 local.name_.assign(new_entry);
269 else
271 fprintf(stderr, "Ignoring obsolete mdp entry '%s'\n",
272 local.name_.c_str());
273 local.bObsolete_ = TRUE;
279 int search_einp(gmx::ArrayRef<const t_inpfile> inp, const char *name)
281 if (inp.empty())
283 return -1;
285 for (gmx::index i = 0; i < inp.ssize(); i++)
287 if (gmx_strcasecmp_min(name, inp[i].name_.c_str()) == 0)
289 return i;
292 return -1;
295 void mark_einp_set(gmx::ArrayRef<t_inpfile> inp, const char *name)
297 int i = search_einp(inp, name);
298 if (i != -1)
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;
308 static int get_einp(std::vector<t_inpfile> *inp, const char *name)
310 std::vector<t_inpfile> &inpRef = *inp;
311 bool notfound = false;
313 int i = search_einp(inpRef, name);
315 if (i == -1)
317 notfound = true;
318 inpRef.emplace_back(0, 0, false, true, false,
319 name, "");
320 i = inpRef.size() - 1;
321 if (inpRef.size() == 1)
323 inpRef.front().inp_count_ = 1;
326 inpRef[i].count_ = inpRef.front().inp_count_++;
327 inpRef[i].bSet_ = TRUE;
328 if (debug)
330 fprintf(debug, "Inp %d = %s\n", inpRef[i].count_, inpRef[i].name_.c_str());
333 if (notfound)
335 return -1;
337 else
339 return i;
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)
347 std::vector<t_inpfile> &inpRef = *inp;
348 char buf[32], *ptr, warn_buf[STRLEN];
350 int ii = get_einp(inp, name);
352 if (ii == -1)
354 sprintf(buf, "%d", def);
355 inpRef.back().value_.assign(buf);
357 return def;
359 else
361 int ret = std::strtol(inpRef[ii].value_.c_str(), &ptr, 10);
362 if (*ptr != '\0')
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);
368 return ret;
372 int get_eint(std::vector<t_inpfile> *inp, const std::string &name, int def,
373 warninp_t wi)
375 return get_eint(inp, name.c_str(), def, wi);
378 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
379 int64_t get_eint64(std::vector<t_inpfile> *inp,
380 const char *name, int64_t def,
381 warninp_t wi)
383 std::vector<t_inpfile> &inpRef = *inp;
384 char buf[32], *ptr, warn_buf[STRLEN];
386 int ii = get_einp(inp, name);
388 if (ii == -1)
390 sprintf(buf, "%" PRId64, def);
391 inpRef.back().value_.assign(buf);
393 return def;
395 else
397 int64_t ret = str_to_int64_t(inpRef[ii].value_.c_str(), &ptr);
398 if (*ptr != '\0')
400 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());
401 warning_error(wi, warn_buf);
404 return ret;
408 int64_t get_eint64(std::vector<t_inpfile> *inp,
409 const std::string &name, int64_t def,
410 warninp_t wi)
412 return get_eint64(inp, name.c_str(), def, wi);
415 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
416 double get_ereal(std::vector<t_inpfile> *inp, const char *name, double def,
417 warninp_t wi)
419 std::vector<t_inpfile> &inpRef = *inp;
420 char buf[32], *ptr, warn_buf[STRLEN];
422 int ii = get_einp(inp, name);
424 if (ii == -1)
426 sprintf(buf, "%g", def);
427 inpRef.back().value_.assign(buf);
429 return def;
431 else
433 double ret = strtod(inpRef[ii].value_.c_str(), &ptr);
434 if (*ptr != '\0')
436 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());
437 warning_error(wi, warn_buf);
440 return ret;
444 double get_ereal(std::vector<t_inpfile> *inp, const std::string &name, double def,
445 warninp_t wi)
447 return get_ereal(inp, name.c_str(), def, wi);
450 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
451 const char *get_estr(std::vector<t_inpfile> *inp, const char *name, const char *def)
453 std::vector<t_inpfile> &inpRef = *inp;
454 char buf[32];
456 int ii = get_einp(inp, name);
458 if (ii == -1)
460 if (def)
462 sprintf(buf, "%s", def);
463 inpRef.back().value_.assign(buf);
465 else
467 inpRef.back().value_.clear();
470 return def;
472 else
474 return inpRef[ii].value_.c_str();
478 const char *get_estr(std::vector<t_inpfile> *inp, const std::string &name, const char *def)
480 return get_estr(inp, name.c_str(), def);
483 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
484 int get_eeenum(std::vector<t_inpfile> *inp, const char *name, const char **defs,
485 warninp_t wi)
487 std::vector<t_inpfile> &inpRef = *inp;
488 int n = 0;
489 char buf[STRLEN];
491 int ii = get_einp(inp, name);
493 if (ii == -1)
495 inpRef.back().value_.assign(defs[0]);
497 return 0;
499 int i = 0;
500 for (i = 0; (defs[i] != nullptr); i++)
502 if (gmx_strcasecmp_min(defs[i], inpRef[ii].value_.c_str()) == 0)
504 break;
508 if (defs[i] == nullptr)
510 n += sprintf(buf, "Invalid enum '%s' for variable %s, using '%s'\n",
511 inpRef[ii].value_.c_str(), name, defs[0]);
512 n += sprintf(buf+n, "Next time use one of:");
513 int j = 0;
514 while (defs[j])
516 n += sprintf(buf+n, " '%s'", defs[j]);
517 j++;
519 if (wi != nullptr)
521 warning_error(wi, buf);
523 else
525 fprintf(stderr, "%s\n", buf);
528 inpRef[ii].value_ = gmx_strdup(defs[0]);
530 return 0;
533 return i;
536 int get_eeenum(std::vector<t_inpfile> *inp, const std::string &name, const char **defs,
537 warninp_t wi)
539 return get_eeenum(inp, name.c_str(), defs, wi);
542 int get_eenum(std::vector<t_inpfile> *inp, const char *name, const char **defs)
544 return get_eeenum(inp, name, defs, nullptr);
547 void
548 printStringNewline(std::vector<t_inpfile> *inp, const char *line)
550 std::string tmp("\n; ");
551 tmp.append(line);
552 get_estr(inp, tmp.c_str(), nullptr);
555 void
556 printStringNoNewline(std::vector<t_inpfile> *inp, const char *line)
558 std::string tmp("; ");
559 tmp.append(line);
560 get_estr(inp, tmp.c_str(), nullptr);
562 void
563 setStringEntry(std::vector<t_inpfile> *inp, const char *name, char *newName, const char *def)
565 const char *found = nullptr;
566 found = get_estr(inp, name, def);
567 if (found != nullptr)
569 std::strcpy(newName, found);