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,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.
52 #include <unordered_set>
53 #include <sys/types.h>
55 #include "gromacs/utility/arrayref.h"
56 #include "gromacs/utility/cstringutil.h"
57 #include "gromacs/utility/dir_separator.h"
58 #include "gromacs/utility/fatalerror.h"
59 #include "gromacs/utility/futil.h"
60 #include "gromacs/utility/gmxassert.h"
68 /* enum used for handling ifdefs */
79 std::shared_ptr<std::vector<t_define>> defines;
80 std::shared_ptr<std::vector<std::string>> includes;
81 std::unordered_set<std::string> unmatched_defines;
88 std::vector<int> ifdefs;
89 struct gmx_cpp* child = nullptr;
90 struct gmx_cpp* parent = nullptr;
93 static bool is_word_end(char c)
95 return !((isalnum(c) != 0) || c == '_');
98 static const char* strstrw(const char* buf, const char* word)
102 while ((ptr = strstr(buf, word)) != nullptr)
104 /* Check if we did not find part of a longer word */
105 if (ptr && is_word_end(ptr[strlen(word)])
106 && (((ptr > buf) && is_word_end(ptr[-1])) || (ptr == buf)))
111 buf = ptr + strlen(word);
116 /* Finds a preprocessor directive, whose name (after the '#') is
117 * returned in *name, and the remainder of the line after leading
118 * whitespace, without trailing whitespace, is returned in *val
120 static bool find_directive(const char* buf, std::string* name, std::string* val)
122 /* Skip initial whitespace */
123 while (isspace(*buf))
127 /* Check if this is a directive */
132 /* Skip the hash and any space after it */
134 while (isspace(*buf))
138 /* Set the name pointer and find the next space */
140 while (*buf != '\0' && !isspace(*buf))
145 /* Set the end of the name here, and skip any space */
149 while (isspace(*buf))
154 /* Check if anything is remaining */
158 // Remove trailing whitespace
159 while (!val->empty() && isspace(val->back()))
161 val->resize(val->size() - 1);
172 static bool is_ifdeffed_out(gmx::ArrayRef<const int> ifdefs)
174 return (!ifdefs.empty() && ifdefs.back() != eifTRUE);
177 static void add_include(std::vector<std::string>* includes, const char* includePath)
179 GMX_RELEASE_ASSERT(includes, "Need valid includes");
180 GMX_RELEASE_ASSERT(includePath, "Need a valid include path");
182 for (const std::string& include : *includes)
184 if (strcmp(include.c_str(), includePath) == 0)
190 includes->push_back(includePath);
193 static void add_define(std::vector<t_define>* defines, const std::string& name, const char* value)
195 GMX_RELEASE_ASSERT(defines, "Need defines");
196 GMX_RELEASE_ASSERT(value, "Need a value");
198 for (t_define& define : *defines)
200 if (define.name == name)
207 defines->push_back({ name, value });
210 /* Open the file to be processed. The handle variable holds internal
211 info for the cpp emulator. Return integer status */
212 static int cpp_open_file(const char* filenm,
215 std::shared_ptr<std::vector<t_define>>* definesFromParent,
216 std::shared_ptr<std::vector<std::string>>* includesFromParent)
218 // TODO: We should avoid new/delete, we should use Pimpl instead
219 gmx_cpp* cpp = new gmx_cpp;
222 if (definesFromParent)
224 cpp->defines = *definesFromParent;
228 cpp->defines = std::make_shared<std::vector<t_define>>();
231 if (includesFromParent)
233 cpp->includes = *includesFromParent;
237 cpp->includes = std::make_shared<std::vector<std::string>>();
240 /* First process options, they might be necessary for opening files
241 (especially include statements). */
247 if (strstr(cppopts[i], "-I") == cppopts[i])
249 add_include(cpp->includes.get(), cppopts[i] + 2);
251 if (strstr(cppopts[i], "-D") == cppopts[i])
253 /* If the option contains a =, split it into name and value. */
254 char* ptr = strchr(cppopts[i], '=');
257 std::string buf = cppopts[i] + 2;
258 buf.resize(ptr - cppopts[i] - 2);
259 add_define(cpp->defines.get(), buf, ptr + 1);
260 cpp->unmatched_defines.insert(buf);
264 add_define(cpp->defines.get(), cppopts[i] + 2, "");
265 cpp->unmatched_defines.insert(cppopts[i] + 2);
272 /* Find the file. First check whether it is in the current directory. */
273 if (gmx_fexist(filenm))
279 /* If not, check all the paths given with -I. */
280 for (const std::string& include : *cpp->includes)
282 std::string buf = include + "/" + filenm;
289 /* If still not found, check the Gromacs library search path. */
292 cpp->fn = gmx::findLibraryFile(filenm, false, false);
297 gmx_fatal(FARGS, "Topology include file \"%s\" not found", filenm);
299 /* If the file name has a path component, we need to change to that
300 * directory. Note that we - just as C - always use UNIX path separators
301 * internally in include file names.
303 size_t pos = cpp->fn.rfind('/');
304 size_t pos2 = cpp->fn.rfind(DIR_SEPARATOR);
306 if (pos == std::string::npos || (pos2 != std::string::npos && pos2 > pos))
310 if (pos != std::string::npos)
313 cpp->path.resize(pos);
314 cpp->fn.erase(0, pos + 1);
317 gmx_getcwd(buf, STRLEN);
320 gmx_chdir(cpp->path.c_str());
325 cpp->child = nullptr;
326 cpp->parent = nullptr;
327 if (cpp->fp == nullptr)
329 cpp->fp = fopen(cpp->fn.c_str(), "r");
331 if (cpp->fp == nullptr)
336 default: return eCPP_UNKNOWN;
342 /* Open the file to be processed. The handle variable holds internal
343 info for the cpp emulator. Return integer status */
344 int cpp_open_file(const char* filenm, gmx_cpp_t* handle, char** cppopts)
346 return cpp_open_file(filenm, handle, cppopts, nullptr, nullptr);
349 /* Note that dval might be null, e.g. when handling a line like '#define */
350 static int process_directive(gmx_cpp_t* handlep, const std::string& dname, const std::string& dval)
352 gmx_cpp_t handle = *handlep;
354 std::vector<int>& ifdefs = handle->ifdefs;
356 /* #ifdef or ifndef statement */
357 bool bIfdef = (dname == "ifdef");
358 bool bIfndef = (dname == "ifndef");
359 if (bIfdef || bIfndef)
361 if (is_ifdeffed_out(ifdefs))
363 handle->ifdefs.push_back(eifIGNORE);
367 // A bare '#ifdef' or '#ifndef' is invalid
373 for (const t_define& define : *handle->defines)
375 if (define.name == dval)
377 // erase from unmatched_defines in original handle
378 gmx_cpp_t root = handle;
379 while (root->parent != nullptr)
383 root->unmatched_defines.erase(dval);
389 if ((bIfdef && found) || (bIfndef && !found))
391 ifdefs.push_back(eifTRUE);
395 ifdefs.push_back(eifFALSE);
401 /* #else statement */
408 if (ifdefs.back() == eifTRUE)
410 ifdefs.back() = eifFALSE;
412 else if (ifdefs.back() == eifFALSE)
414 ifdefs.back() = eifTRUE;
419 /* #endif statement */
420 if (dname == "endif")
426 ifdefs.erase(ifdefs.end() - 1);
430 /* Check whether we're not ifdeffed out. The order of this statement
431 is important. It has to come after #ifdef, #else and #endif, but
432 anything else should be ignored. */
433 if (is_ifdeffed_out(ifdefs))
438 /* Check for include statements */
439 if (dname == "include")
443 // A bare '#include' is an invalid line
448 // An include needs to be followed by either a '"' or a '<' as a first character.
449 if ((dval[0] != '"') && (dval[0] != '<'))
451 return eCPP_INVALID_INCLUDE_DELIMITER;
453 for (size_t i1 = 0; i1 < dval.size(); i1++)
455 if ((dval[i1] == '"') || (dval[i1] == '<') || (dval[i1] == '>'))
476 std::string inc_fn = dval.substr(i0, len);
478 /* Open include file and store it as a child in the handle structure */
479 int status = cpp_open_file(inc_fn.c_str(), &(handle->child), nullptr, &handle->defines,
481 if (status != eCPP_OK)
483 handle->child = nullptr;
486 /* Make a linked list of open files and move on to the include file */
487 handle->child->parent = handle;
488 *handlep = handle->child;
492 /* #define statement */
493 if (dname == "define")
495 // A bare '#define' is an invalid line
500 /* Split it into name and value. */
501 const char* ptr = dval.c_str();
502 while ((*ptr != '\0') && !isspace(*ptr))
506 std::string name = dval.substr(0, ptr - dval.c_str());
508 while ((*ptr != '\0') && isspace(*ptr))
513 add_define(handle->defines.get(), name, ptr);
517 /* #undef statement */
518 if (dname == "undef")
520 // A bare '#undef' is an invalid line
525 std::vector<t_define>& defines = *handle->defines;
526 for (size_t i = 0; i < defines.size(); i++)
528 if (defines[i].name == dval)
530 defines.erase(defines.begin() + i);
538 /* If we haven't matched anything, this is an unknown directive */
542 /* Return one whole line from the file into buf which holds at most n
543 characters, for subsequent processing. Returns integer status. This
544 routine also does all the "intelligent" work like processing cpp
545 directives and so on. Note that often the routine is called
546 recursively and no cpp directives are printed. */
547 int cpp_read_line(gmx_cpp_t* handlep, int n, char buf[])
549 gmx_cpp_t handle = *handlep;
555 return eCPP_INVALID_HANDLE;
559 return eCPP_FILE_NOT_OPEN;
562 bEOF = (feof(handle->fp) != 0);
565 /* Read the actual line now. */
566 if (fgets2(buf, n - 1, handle->fp) == nullptr)
568 /* Recheck EOF, since we could have been at the end before
569 * the fgets2 call, but we need to read past the end to know.
571 bEOF = (feof(handle->fp) != 0);
574 /* Something strange happened, fgets returned NULL,
575 * but we are not at EOF. Maybe wrong line endings?
584 if (handle->parent == nullptr)
588 cpp_close_file(handlep);
589 *handlep = handle->parent;
591 return cpp_read_line(handlep, n, buf);
597 } /* Now we've read a line! */
599 /* Process directives if this line contains one */
602 if (find_directive(buf, &dname, &dval))
604 status = process_directive(handlep, dname, dval);
605 if (status != eCPP_OK)
609 /* Don't print lines with directives, go on to the next */
610 return cpp_read_line(handlep, n, buf);
613 /* Check whether we're not ifdeffed out. The order of this statement
614 is important. It has to come after #ifdef, #else and #endif, but
615 anything else should be ignored. */
616 if (is_ifdeffed_out(handle->ifdefs))
618 return cpp_read_line(handlep, n, buf);
621 /* Check whether we have any defines that need to be replaced. Note
622 that we have to use a best fit algorithm, rather than first come
623 first go. We do this by sorting the defines on length first, and
624 then on alphabetical order. */
625 for (t_define& define : *handle->defines)
627 if (!define.def.empty())
630 const char* ptr = buf;
631 while ((ptr = strstrw(ptr, define.name.c_str())) != nullptr)
634 ptr += strlen(define.name.c_str());
638 // Need to erase unmatched define in original handle
639 gmx_cpp_t root = handle;
640 while (root->parent != nullptr)
644 root->unmatched_defines.erase(define.name);
647 const char* ptr = buf;
649 while ((ptr2 = strstrw(ptr, define.name.c_str())) != nullptr)
651 name.append(ptr, ptr2 - ptr);
653 ptr = ptr2 + define.name.size();
656 GMX_RELEASE_ASSERT(name.size() < static_cast<size_t>(n),
657 "The line should fit in buf");
658 strcpy(buf, name.c_str());
666 const char* cpp_cur_file(const gmx_cpp_t* handlep)
668 return (*handlep)->fn.c_str();
671 int cpp_cur_linenr(const gmx_cpp_t* handlep)
673 return (*handlep)->line_nr;
676 /* Close the file! Return integer status. */
677 int cpp_close_file(gmx_cpp_t* handlep)
679 gmx_cpp_t handle = *handlep;
683 return eCPP_INVALID_HANDLE;
687 return eCPP_FILE_NOT_OPEN;
691 if (!handle->cwd.empty())
693 gmx_chdir(handle->cwd.c_str());
696 handle->fp = nullptr;
698 handle->line.clear();
703 const std::string* cpp_find_define(const gmx_cpp_t* handlep, const std::string& defineName)
705 for (const t_define& define : *(*handlep)->defines)
707 if (define.name == defineName)
716 void cpp_done(gmx_cpp_t handle)
718 int status = cpp_close_file(&handle);
719 if (status != eCPP_OK)
721 gmx_fatal(FARGS, "%s", cpp_error(&handle, status));
726 /* Return a string containing the error message coresponding to status
728 char* cpp_error(gmx_cpp_t* handlep, int status)
731 const char* ecpp[] = { "OK",
736 "Invalid file handle",
737 "Invalid delimiter for filename in #include statement",
739 "Unknown error, perhaps your text file uses wrong line endings?",
740 "Error status out of range" };
741 gmx_cpp_t handle = *handlep;
745 return const_cast<char*>(ecpp[eCPP_INVALID_HANDLE]);
748 if ((status < 0) || (status >= eCPP_NR))
753 sprintf(buf, "%s - File %s, line %d\nLast line read:\n'%s'", ecpp[status],
754 (handle && !handle->fn.empty()) ? handle->fn.c_str() : "unknown",
755 (handle) ? handle->line_nr : -1, !handle->line.empty() ? handle->line.c_str() : "");
757 return gmx_strdup(buf);
760 std::string checkAndWarnForUnusedDefines(const gmx_cpp& handle)
763 if (!handle.unmatched_defines.empty())
766 "The following macros were defined in the 'define' mdp field with the -D prefix, "
768 "were not used in the topology:\n";
769 for (auto& str : handle.unmatched_defines)
771 warning += (" " + str + "\n");
774 "If you haven't made a spelling error, either use the macro you defined, "
775 "or don't define the macro";