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 */
70 eifTRUE
, eifFALSE
, eifIGNORE
, eifNR
75 std::shared_ptr
< std::vector
< t_define
>> defines
;
76 std::shared_ptr
< std::vector
< std::string
>> includes
;
77 std::unordered_set
<std::string
> unmatched_defines
;
84 std::vector
<int> ifdefs
;
85 struct gmx_cpp
*child
= nullptr;
86 struct gmx_cpp
*parent
= nullptr;
89 static bool is_word_end(char c
)
91 return !((isalnum(c
) != 0) || c
== '_');
94 static const char *strstrw(const char *buf
, const char *word
)
98 while ((ptr
= strstr(buf
, word
)) != nullptr)
100 /* Check if we did not find part of a longer word */
102 is_word_end(ptr
[strlen(word
)]) &&
103 (((ptr
> buf
) && is_word_end(ptr
[-1])) || (ptr
== buf
)))
108 buf
= ptr
+ strlen(word
);
113 /* Finds a preprocessor directive, whose name (after the '#') is
114 * returned in *name, and the remainder of the line after leading
115 * whitespace, without trailing whitespace, is returned in *val
117 static bool find_directive(const char *buf
,
121 /* Skip initial whitespace */
122 while (isspace(*buf
))
126 /* Check if this is a directive */
131 /* Skip the hash and any space after it */
133 while (isspace(*buf
))
137 /* Set the name pointer and find the next space */
139 while (*buf
!= '\0' && !isspace(*buf
))
144 /* Set the end of the name here, and skip any space */
148 while (isspace(*buf
))
153 /* Check if anything is remaining */
157 // Remove trailing whitespace
158 while (!val
->empty() && isspace(val
->back()))
160 val
->resize(val
->size() - 1);
171 static bool is_ifdeffed_out(gmx::ArrayRef
<const int> ifdefs
)
173 return (!ifdefs
.empty() && ifdefs
.back() != eifTRUE
);
176 static void add_include(std::vector
<std::string
> *includes
,
177 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
,
194 const std::string
&name
,
197 GMX_RELEASE_ASSERT(defines
, "Need defines");
198 GMX_RELEASE_ASSERT(value
, "Need a value");
200 for (t_define
&define
: *defines
)
202 if (define
.name
== name
)
209 defines
->push_back({ name
, value
});
212 /* Open the file to be processed. The handle variable holds internal
213 info for the cpp emulator. Return integer status */
215 cpp_open_file(const char *filenm
,
218 std::shared_ptr
< std::vector
< t_define
>> *definesFromParent
,
219 std::shared_ptr
< std::vector
< std::string
>> *includesFromParent
)
221 // TODO: We should avoid new/delete, we should use Pimpl instead
222 gmx_cpp
*cpp
= new gmx_cpp
;
225 if (definesFromParent
)
227 cpp
->defines
= *definesFromParent
;
231 cpp
->defines
= std::make_shared
< std::vector
< t_define
>>();
234 if (includesFromParent
)
236 cpp
->includes
= *includesFromParent
;
240 cpp
->includes
= std::make_shared
< std::vector
< std::string
>>();
243 /* First process options, they might be necessary for opening files
244 (especially include statements). */
250 if (strstr(cppopts
[i
], "-I") == cppopts
[i
])
252 add_include(cpp
->includes
.get(), cppopts
[i
] + 2);
254 if (strstr(cppopts
[i
], "-D") == cppopts
[i
])
256 /* If the option contains a =, split it into name and value. */
257 char *ptr
= strchr(cppopts
[i
], '=');
260 std::string buf
= cppopts
[i
] + 2;
261 buf
.resize(ptr
- cppopts
[i
] - 2);
262 add_define(cpp
->defines
.get(), buf
, ptr
+ 1);
263 cpp
->unmatched_defines
.insert(buf
);
268 add_define(cpp
->defines
.get(), cppopts
[i
] + 2, "");
269 cpp
->unmatched_defines
.insert(cppopts
[i
] + 2);
276 /* Find the file. First check whether it is in the current directory. */
277 if (gmx_fexist(filenm
))
283 /* If not, check all the paths given with -I. */
284 for (const std::string
&include
: *cpp
->includes
)
286 std::string buf
= include
+ "/" + filenm
;
293 /* If still not found, check the Gromacs library search path. */
296 cpp
->fn
= gmx::findLibraryFile(filenm
, false, false);
301 gmx_fatal(FARGS
, "Topology include file \"%s\" not found", filenm
);
303 /* If the file name has a path component, we need to change to that
304 * directory. Note that we - just as C - always use UNIX path separators
305 * internally in include file names.
307 size_t pos
= cpp
->fn
.rfind('/');
308 size_t pos2
= cpp
->fn
.rfind(DIR_SEPARATOR
);
310 if (pos
== std::string::npos
|| (pos2
!= std::string::npos
&& pos2
> pos
))
314 if (pos
!= std::string::npos
)
317 cpp
->path
.resize(pos
);
318 cpp
->fn
.erase(0, pos
+ 1);
321 gmx_getcwd(buf
, STRLEN
);
324 gmx_chdir(cpp
->path
.c_str());
329 cpp
->child
= nullptr;
330 cpp
->parent
= nullptr;
331 if (cpp
->fp
== nullptr)
333 cpp
->fp
= fopen(cpp
->fn
.c_str(), "r");
335 if (cpp
->fp
== nullptr)
347 /* Open the file to be processed. The handle variable holds internal
348 info for the cpp emulator. Return integer status */
349 int cpp_open_file(const char *filenm
, gmx_cpp_t
*handle
, char **cppopts
)
351 return cpp_open_file(filenm
, handle
, cppopts
, nullptr, nullptr);
354 /* Note that dval might be null, e.g. when handling a line like '#define */
356 process_directive(gmx_cpp_t
*handlep
,
357 const std::string
&dname
,
358 const std::string
&dval
)
360 gmx_cpp_t handle
= *handlep
;
362 std::vector
<int> &ifdefs
= handle
->ifdefs
;
364 /* #ifdef or ifndef statement */
365 bool bIfdef
= (dname
== "ifdef");
366 bool bIfndef
= (dname
== "ifndef");
367 if (bIfdef
|| bIfndef
)
369 if (is_ifdeffed_out(ifdefs
))
371 handle
->ifdefs
.push_back(eifIGNORE
);
375 // A bare '#ifdef' or '#ifndef' is invalid
381 for (const t_define
&define
: *handle
->defines
)
383 if (define
.name
== dval
)
385 // erase from unmatched_defines in original handle
386 gmx_cpp_t root
= handle
;
387 while (root
->parent
!= nullptr)
391 root
->unmatched_defines
.erase(dval
);
397 if ((bIfdef
&& found
) || (bIfndef
&& !found
))
399 ifdefs
.push_back(eifTRUE
);
403 ifdefs
.push_back(eifFALSE
);
409 /* #else statement */
416 if (ifdefs
.back() == eifTRUE
)
418 ifdefs
.back() = eifFALSE
;
420 else if (ifdefs
.back() == eifFALSE
)
422 ifdefs
.back() = eifTRUE
;
427 /* #endif statement */
428 if (dname
== "endif")
434 ifdefs
.erase(ifdefs
.end() - 1);
438 /* Check whether we're not ifdeffed out. The order of this statement
439 is important. It has to come after #ifdef, #else and #endif, but
440 anything else should be ignored. */
441 if (is_ifdeffed_out(ifdefs
))
446 /* Check for include statements */
447 if (dname
== "include")
451 // A bare '#include' is an invalid line
456 // An include needs to be followed by either a '"' or a '<' as a first character.
457 if ((dval
[0] != '"') && (dval
[0] != '<'))
459 return eCPP_INVALID_INCLUDE_DELIMITER
;
461 for (size_t i1
= 0; i1
< dval
.size(); i1
++)
463 if ((dval
[i1
] == '"') || (dval
[i1
] == '<') || (dval
[i1
] == '>'))
484 std::string inc_fn
= dval
.substr(i0
, len
);
486 /* Open include file and store it as a child in the handle structure */
487 int status
= cpp_open_file(inc_fn
.c_str(), &(handle
->child
), nullptr,
488 &handle
->defines
, &handle
->includes
);
489 if (status
!= eCPP_OK
)
491 handle
->child
= nullptr;
494 /* Make a linked list of open files and move on to the include file */
495 handle
->child
->parent
= handle
;
496 *handlep
= handle
->child
;
500 /* #define statement */
501 if (dname
== "define")
503 // A bare '#define' is an invalid line
508 /* Split it into name and value. */
509 const char *ptr
= dval
.c_str();
510 while ((*ptr
!= '\0') && !isspace(*ptr
))
514 std::string name
= dval
.substr(0, ptr
- dval
.c_str());
516 while ((*ptr
!= '\0') && isspace(*ptr
))
521 add_define(handle
->defines
.get(), name
, ptr
);
525 /* #undef statement */
526 if (dname
== "undef")
528 // A bare '#undef' is an invalid line
533 std::vector
<t_define
> &defines
= *handle
->defines
;
534 for (size_t i
= 0; i
< defines
.size(); i
++)
536 if (defines
[i
].name
== dval
)
538 defines
.erase(defines
.begin() + i
);
546 /* If we haven't matched anything, this is an unknown directive */
550 /* Return one whole line from the file into buf which holds at most n
551 characters, for subsequent processing. Returns integer status. This
552 routine also does all the "intelligent" work like processing cpp
553 directives and so on. Note that often the routine is called
554 recursively and no cpp directives are printed. */
555 int cpp_read_line(gmx_cpp_t
*handlep
, int n
, char buf
[])
557 gmx_cpp_t handle
= *handlep
;
563 return eCPP_INVALID_HANDLE
;
567 return eCPP_FILE_NOT_OPEN
;
570 bEOF
= (feof(handle
->fp
) != 0);
573 /* Read the actual line now. */
574 if (fgets2(buf
, n
-1, handle
->fp
) == nullptr)
576 /* Recheck EOF, since we could have been at the end before
577 * the fgets2 call, but we need to read past the end to know.
579 bEOF
= (feof(handle
->fp
) != 0);
582 /* Something strange happened, fgets returned NULL,
583 * but we are not at EOF. Maybe wrong line endings?
592 if (handle
->parent
== nullptr)
596 cpp_close_file(handlep
);
597 *handlep
= handle
->parent
;
599 return cpp_read_line(handlep
, n
, buf
);
605 } /* Now we've read a line! */
607 /* Process directives if this line contains one */
610 if (find_directive(buf
, &dname
, &dval
))
612 status
= process_directive(handlep
, dname
, dval
);
613 if (status
!= eCPP_OK
)
617 /* Don't print lines with directives, go on to the next */
618 return cpp_read_line(handlep
, n
, buf
);
621 /* Check whether we're not ifdeffed out. The order of this statement
622 is important. It has to come after #ifdef, #else and #endif, but
623 anything else should be ignored. */
624 if (is_ifdeffed_out(handle
->ifdefs
))
626 return cpp_read_line(handlep
, n
, buf
);
629 /* Check whether we have any defines that need to be replaced. Note
630 that we have to use a best fit algorithm, rather than first come
631 first go. We do this by sorting the defines on length first, and
632 then on alphabetical order. */
633 for (t_define
&define
: *handle
->defines
)
635 if (!define
.def
.empty())
638 const char *ptr
= buf
;
639 while ((ptr
= strstrw(ptr
, define
.name
.c_str())) != nullptr)
642 ptr
+= strlen(define
.name
.c_str());
646 // Need to erase unmatched define in original handle
647 gmx_cpp_t root
= handle
;
648 while (root
->parent
!= nullptr)
652 root
->unmatched_defines
.erase(define
.name
);
655 const char *ptr
= buf
;
657 while ((ptr2
= strstrw(ptr
, define
.name
.c_str())) != nullptr)
659 name
.append(ptr
, ptr2
- ptr
);
661 ptr
= ptr2
+ define
.name
.size();
664 GMX_RELEASE_ASSERT(name
.size() < static_cast<size_t>(n
),
665 "The line should fit in buf");
666 strcpy(buf
, name
.c_str());
674 const char *cpp_cur_file(const gmx_cpp_t
*handlep
)
676 return (*handlep
)->fn
.c_str();
679 int cpp_cur_linenr(const gmx_cpp_t
*handlep
)
681 return (*handlep
)->line_nr
;
684 /* Close the file! Return integer status. */
685 int cpp_close_file(gmx_cpp_t
*handlep
)
687 gmx_cpp_t handle
= *handlep
;
691 return eCPP_INVALID_HANDLE
;
695 return eCPP_FILE_NOT_OPEN
;
699 if (!handle
->cwd
.empty())
701 gmx_chdir(handle
->cwd
.c_str());
704 handle
->fp
= nullptr;
706 handle
->line
.clear();
711 const std::string
*cpp_find_define(const gmx_cpp_t
*handlep
,
712 const std::string
&defineName
)
714 for (const t_define
&define
: *(*handlep
)->defines
)
716 if (define
.name
== defineName
)
725 void cpp_done(gmx_cpp_t handle
)
727 int status
= cpp_close_file(&handle
);
728 if (status
!= eCPP_OK
)
730 gmx_fatal(FARGS
, "%s", cpp_error(&handle
, status
));
735 /* Return a string containing the error message coresponding to status
737 char *cpp_error(gmx_cpp_t
*handlep
, int status
)
740 const char *ecpp
[] = {
741 "OK", "File not found", "End of file", "Syntax error", "Interrupted",
742 "Invalid file handle", "Invalid delimiter for filename in #include statement",
743 "File not open", "Unknown error, perhaps your text file uses wrong line endings?", "Error status out of range"
745 gmx_cpp_t handle
= *handlep
;
749 return const_cast<char *>(ecpp
[eCPP_INVALID_HANDLE
]);
752 if ((status
< 0) || (status
>= eCPP_NR
))
757 sprintf(buf
, "%s - File %s, line %d\nLast line read:\n'%s'",
759 (handle
&& !handle
->fn
.empty()) ? handle
->fn
.c_str() : "unknown",
760 (handle
) ? handle
->line_nr
: -1,
761 !handle
->line
.empty() ? handle
->line
.c_str() : "");
763 return gmx_strdup(buf
);
766 std::string
checkAndWarnForUnusedDefines(const gmx_cpp
&handle
)
769 if (!handle
.unmatched_defines
.empty())
771 warning
= "The following macros were defined in the 'define' mdp field with the -D prefix, but "
772 "were not used in the topology:\n";
773 for (auto &str
: handle
.unmatched_defines
)
775 warning
+= (" " + str
+ "\n");
777 warning
+= "If you haven't made a spelling error, either use the macro you defined, "
778 "or don't define the macro";