c359c7a450851cbabcafe7686df08df1b98075ce
[alexxy/gromacs.git] / src / gromacs / gmxpreprocess / gmxcpp.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,2017,2018 by the GROMACS development team.
7  * Copyright (c) 2019,2020, by the GROMACS development team, led by
8  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
9  * and including many others, as listed in the AUTHORS file in the
10  * top-level source directory and at http://www.gromacs.org.
11  *
12  * GROMACS is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public License
14  * as published by the Free Software Foundation; either version 2.1
15  * of the License, or (at your option) any later version.
16  *
17  * GROMACS is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with GROMACS; if not, see
24  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
26  *
27  * If you want to redistribute modifications to GROMACS, please
28  * consider that scientific software is very special. Version
29  * control is crucial - bugs must be traceable. We will be happy to
30  * consider code for inclusion in the official distribution, but
31  * derived work must not be called official GROMACS. Details are found
32  * in the README & COPYING files - if they are missing, get the
33  * official version at http://www.gromacs.org.
34  *
35  * To help us fund GROMACS development, we humbly ask that you cite
36  * the research papers on the package. Check out http://www.gromacs.org.
37  */
38 #include "gmxpre.h"
39
40 #include "gmxcpp.h"
41
42 #include <cctype>
43 #include <cerrno>
44 #include <climits>
45 #include <cmath>
46 #include <cstdio>
47 #include <cstdlib>
48 #include <cstring>
49
50 #include <algorithm>
51 #include <memory>
52 #include <unordered_set>
53
54 #include <sys/types.h>
55
56 #include "gromacs/utility/arrayref.h"
57 #include "gromacs/utility/cstringutil.h"
58 #include "gromacs/utility/dir_separator.h"
59 #include "gromacs/utility/fatalerror.h"
60 #include "gromacs/utility/futil.h"
61 #include "gromacs/utility/gmxassert.h"
62
63 struct t_define
64 {
65     std::string name;
66     std::string def;
67 };
68
69 /* enum used for handling ifdefs */
70 enum
71 {
72     eifTRUE,
73     eifFALSE,
74     eifIGNORE,
75     eifNR
76 };
77
78 struct gmx_cpp
79 {
80     std::shared_ptr<std::vector<t_define>>    defines;
81     std::shared_ptr<std::vector<std::string>> includes;
82     std::unordered_set<std::string>           unmatched_defines;
83     FILE*                                     fp = nullptr;
84     std::string                               path;
85     std::string                               cwd;
86     std::string                               fn;
87     std::string                               line;
88     int                                       line_nr;
89     std::vector<int>                          ifdefs;
90     struct gmx_cpp*                           child  = nullptr;
91     struct gmx_cpp*                           parent = nullptr;
92 };
93
94 static bool is_word_end(char c)
95 {
96     return !((isalnum(c) != 0) || c == '_');
97 }
98
99 static const char* strstrw(const char* buf, const char* word)
100 {
101     const char* ptr;
102
103     while ((ptr = strstr(buf, word)) != nullptr)
104     {
105         /* Check if we did not find part of a longer word */
106         if (ptr && is_word_end(ptr[strlen(word)])
107             && (((ptr > buf) && is_word_end(ptr[-1])) || (ptr == buf)))
108         {
109             return ptr;
110         }
111
112         buf = ptr + strlen(word);
113     }
114     return nullptr;
115 }
116
117 /* Finds a preprocessor directive, whose name (after the '#') is
118  * returned in *name, and the remainder of the line after leading
119  * whitespace, without trailing whitespace, is returned in *val
120  */
121 static bool find_directive(const char* buf, std::string* name, std::string* val)
122 {
123     /* Skip initial whitespace */
124     while (isspace(*buf))
125     {
126         ++buf;
127     }
128     /* Check if this is a directive */
129     if (*buf != '#')
130     {
131         return FALSE;
132     }
133     /* Skip the hash and any space after it */
134     ++buf;
135     while (isspace(*buf))
136     {
137         ++buf;
138     }
139     /* Set the name pointer and find the next space */
140     name->clear();
141     while (*buf != '\0' && !isspace(*buf))
142     {
143         *name += *buf;
144         ++buf;
145     }
146     /* Set the end of the name here, and skip any space */
147     if (*buf != '\0')
148     {
149         ++buf;
150         while (isspace(*buf))
151         {
152             ++buf;
153         }
154     }
155     /* Check if anything is remaining */
156     if (*buf != '\0')
157     {
158         *val = buf;
159         // Remove trailing whitespace
160         while (!val->empty() && isspace(val->back()))
161         {
162             val->resize(val->size() - 1);
163         }
164     }
165     else
166     {
167         val->clear();
168     }
169
170     return TRUE;
171 }
172
173 static bool is_ifdeffed_out(gmx::ArrayRef<const int> ifdefs)
174 {
175     return (!ifdefs.empty() && ifdefs.back() != eifTRUE);
176 }
177
178 static void add_include(std::vector<std::string>* includes, const char* includePath)
179 {
180     GMX_RELEASE_ASSERT(includes, "Need valid includes");
181     GMX_RELEASE_ASSERT(includePath, "Need a valid include path");
182
183     for (const std::string& include : *includes)
184     {
185         if (strcmp(include.c_str(), includePath) == 0)
186         {
187             return;
188         }
189     }
190
191     includes->push_back(includePath);
192 }
193
194 static void add_define(std::vector<t_define>* defines, const std::string& name, const char* value)
195 {
196     GMX_RELEASE_ASSERT(defines, "Need defines");
197     GMX_RELEASE_ASSERT(value, "Need a value");
198
199     for (t_define& define : *defines)
200     {
201         if (define.name == name)
202         {
203             define.def = value;
204             return;
205         }
206     }
207
208     defines->push_back({ name, value });
209 }
210
211 /* Open the file to be processed. The handle variable holds internal
212    info for the cpp emulator. Return integer status */
213 static int cpp_open_file(const char*                                filenm,
214                          gmx_cpp_t*                                 handle,
215                          char**                                     cppopts,
216                          std::shared_ptr<std::vector<t_define>>*    definesFromParent,
217                          std::shared_ptr<std::vector<std::string>>* includesFromParent)
218 {
219     // TODO: We should avoid new/delete, we should use Pimpl instead
220     gmx_cpp* cpp = new gmx_cpp;
221     *handle      = cpp;
222
223     if (definesFromParent)
224     {
225         cpp->defines = *definesFromParent;
226     }
227     else
228     {
229         cpp->defines = std::make_shared<std::vector<t_define>>();
230     }
231
232     if (includesFromParent)
233     {
234         cpp->includes = *includesFromParent;
235     }
236     else
237     {
238         cpp->includes = std::make_shared<std::vector<std::string>>();
239     }
240
241     /* First process options, they might be necessary for opening files
242        (especially include statements). */
243     int i = 0;
244     if (cppopts)
245     {
246         while (cppopts[i])
247         {
248             if (strstr(cppopts[i], "-I") == cppopts[i])
249             {
250                 add_include(cpp->includes.get(), cppopts[i] + 2);
251             }
252             if (strstr(cppopts[i], "-D") == cppopts[i])
253             {
254                 /* If the option contains a =, split it into name and value. */
255                 char* ptr = strchr(cppopts[i], '=');
256                 if (ptr)
257                 {
258                     std::string buf = cppopts[i] + 2;
259                     buf.resize(ptr - cppopts[i] - 2);
260                     add_define(cpp->defines.get(), buf, ptr + 1);
261                     cpp->unmatched_defines.insert(buf);
262                 }
263                 else
264                 {
265                     add_define(cpp->defines.get(), cppopts[i] + 2, "");
266                     cpp->unmatched_defines.insert(cppopts[i] + 2);
267                 }
268             }
269             i++;
270         }
271     }
272
273     /* Find the file. First check whether it is in the current directory. */
274     if (gmx_fexist(filenm))
275     {
276         cpp->fn = filenm;
277     }
278     else
279     {
280         /* If not, check all the paths given with -I. */
281         for (const std::string& include : *cpp->includes)
282         {
283             std::string buf = include + "/" + filenm;
284             if (gmx_fexist(buf))
285             {
286                 cpp->fn = buf;
287                 break;
288             }
289         }
290         /* If still not found, check the Gromacs library search path. */
291         if (cpp->fn.empty())
292         {
293             cpp->fn = gmx::findLibraryFile(filenm, false, false);
294         }
295     }
296     if (cpp->fn.empty())
297     {
298         gmx_fatal(FARGS, "Topology include file \"%s\" not found", filenm);
299     }
300     /* If the file name has a path component, we need to change to that
301      * directory. Note that we - just as C - always use UNIX path separators
302      * internally in include file names.
303      */
304     size_t pos  = cpp->fn.rfind('/');
305     size_t pos2 = cpp->fn.rfind(DIR_SEPARATOR);
306
307     if (pos == std::string::npos || (pos2 != std::string::npos && pos2 > pos))
308     {
309         pos = pos2;
310     }
311     if (pos != std::string::npos)
312     {
313         cpp->path = cpp->fn;
314         cpp->path.resize(pos);
315         cpp->fn.erase(0, pos + 1);
316
317         char buf[STRLEN];
318         gmx_getcwd(buf, STRLEN);
319         cpp->cwd = buf;
320
321         gmx_chdir(cpp->path.c_str());
322     }
323     cpp->line.clear();
324     cpp->line_nr = 0;
325     cpp->ifdefs.clear();
326     cpp->child  = nullptr;
327     cpp->parent = nullptr;
328     if (cpp->fp == nullptr)
329     {
330         cpp->fp = fopen(cpp->fn.c_str(), "r");
331     }
332     if (cpp->fp == nullptr)
333     {
334         switch (errno)
335         {
336             case EINVAL:
337             default: return eCPP_UNKNOWN;
338         }
339     }
340     return eCPP_OK;
341 }
342
343 /* Open the file to be processed. The handle variable holds internal
344    info for the cpp emulator. Return integer status */
345 int cpp_open_file(const char* filenm, gmx_cpp_t* handle, char** cppopts)
346 {
347     return cpp_open_file(filenm, handle, cppopts, nullptr, nullptr);
348 }
349
350 /* Note that dval might be null, e.g. when handling a line like '#define */
351 static int process_directive(gmx_cpp_t* handlep, const std::string& dname, const std::string& dval)
352 {
353     gmx_cpp_t handle = *handlep;
354
355     std::vector<int>& ifdefs = handle->ifdefs;
356
357     /* #ifdef or ifndef statement */
358     bool bIfdef  = (dname == "ifdef");
359     bool bIfndef = (dname == "ifndef");
360     if (bIfdef || bIfndef)
361     {
362         if (is_ifdeffed_out(ifdefs))
363         {
364             handle->ifdefs.push_back(eifIGNORE);
365         }
366         else
367         {
368             // A bare '#ifdef' or '#ifndef' is invalid
369             if (dval.empty())
370             {
371                 return eCPP_SYNTAX;
372             }
373             bool found = false;
374             for (const t_define& define : *handle->defines)
375             {
376                 if (define.name == dval)
377                 {
378                     // erase from unmatched_defines in original handle
379                     gmx_cpp_t root = handle;
380                     while (root->parent != nullptr)
381                     {
382                         root = root->parent;
383                     }
384                     root->unmatched_defines.erase(dval);
385
386                     found = true;
387                     break;
388                 }
389             }
390             if ((bIfdef && found) || (bIfndef && !found))
391             {
392                 ifdefs.push_back(eifTRUE);
393             }
394             else
395             {
396                 ifdefs.push_back(eifFALSE);
397             }
398         }
399         return eCPP_OK;
400     }
401
402     /* #else statement */
403     if (dname == "else")
404     {
405         if (ifdefs.empty())
406         {
407             return eCPP_SYNTAX;
408         }
409         if (ifdefs.back() == eifTRUE)
410         {
411             ifdefs.back() = eifFALSE;
412         }
413         else if (ifdefs.back() == eifFALSE)
414         {
415             ifdefs.back() = eifTRUE;
416         }
417         return eCPP_OK;
418     }
419
420     /* #endif statement */
421     if (dname == "endif")
422     {
423         if (ifdefs.empty())
424         {
425             return eCPP_SYNTAX;
426         }
427         ifdefs.erase(ifdefs.end() - 1);
428         return eCPP_OK;
429     }
430
431     /* Check whether we're not ifdeffed out. The order of this statement
432        is important. It has to come after #ifdef, #else and #endif, but
433        anything else should be ignored. */
434     if (is_ifdeffed_out(ifdefs))
435     {
436         return eCPP_OK;
437     }
438
439     /* Check for include statements */
440     if (dname == "include")
441     {
442         int len = -1;
443         int i0  = 0;
444         // A bare '#include' is an invalid line
445         if (dval.empty())
446         {
447             return eCPP_SYNTAX;
448         }
449         // An include needs to be followed by either a '"' or a '<' as a first character.
450         if ((dval[0] != '"') && (dval[0] != '<'))
451         {
452             return eCPP_INVALID_INCLUDE_DELIMITER;
453         }
454         for (size_t i1 = 0; i1 < dval.size(); i1++)
455         {
456             if ((dval[i1] == '"') || (dval[i1] == '<') || (dval[i1] == '>'))
457             {
458                 if (len == -1)
459                 {
460                     i0  = i1 + 1;
461                     len = 0;
462                 }
463                 else
464                 {
465                     break;
466                 }
467             }
468             else if (len >= 0)
469             {
470                 len++;
471             }
472         }
473         if (len == -1)
474         {
475             return eCPP_SYNTAX;
476         }
477         std::string inc_fn = dval.substr(i0, len);
478
479         /* Open include file and store it as a child in the handle structure */
480         int status = cpp_open_file(inc_fn.c_str(), &(handle->child), nullptr, &handle->defines,
481                                    &handle->includes);
482         if (status != eCPP_OK)
483         {
484             handle->child = nullptr;
485             return status;
486         }
487         /* Make a linked list of open files and move on to the include file */
488         handle->child->parent = handle;
489         *handlep              = handle->child;
490         return eCPP_OK;
491     }
492
493     /* #define statement */
494     if (dname == "define")
495     {
496         // A bare '#define' is an invalid line
497         if (dval.empty())
498         {
499             return eCPP_SYNTAX;
500         }
501         /* Split it into name and value. */
502         const char* ptr = dval.c_str();
503         while ((*ptr != '\0') && !isspace(*ptr))
504         {
505             ptr++;
506         }
507         std::string name = dval.substr(0, ptr - dval.c_str());
508
509         while ((*ptr != '\0') && isspace(*ptr))
510         {
511             ptr++;
512         }
513
514         add_define(handle->defines.get(), name, ptr);
515         return eCPP_OK;
516     }
517
518     /* #undef statement */
519     if (dname == "undef")
520     {
521         // A bare '#undef' is an invalid line
522         if (dval.empty())
523         {
524             return eCPP_SYNTAX;
525         }
526         std::vector<t_define>& defines = *handle->defines;
527         for (size_t i = 0; i < defines.size(); i++)
528         {
529             if (defines[i].name == dval)
530             {
531                 defines.erase(defines.begin() + i);
532                 break;
533             }
534         }
535
536         return eCPP_OK;
537     }
538
539     /* If we haven't matched anything, this is an unknown directive */
540     return eCPP_SYNTAX;
541 }
542
543 /* Return one whole line from the file into buf which holds at most n
544    characters, for subsequent processing. Returns integer status. This
545    routine also does all the "intelligent" work like processing cpp
546    directives and so on. Note that often the routine is called
547    recursively and no cpp directives are printed. */
548 int cpp_read_line(gmx_cpp_t* handlep, int n, char buf[])
549 {
550     gmx_cpp_t handle = *handlep;
551     int       status;
552     bool      bEOF;
553
554     if (!handle)
555     {
556         return eCPP_INVALID_HANDLE;
557     }
558     if (!handle->fp)
559     {
560         return eCPP_FILE_NOT_OPEN;
561     }
562
563     bEOF = (feof(handle->fp) != 0);
564     if (!bEOF)
565     {
566         /* Read the actual line now. */
567         if (fgets2(buf, n - 1, handle->fp) == nullptr)
568         {
569             /* Recheck EOF, since we could have been at the end before
570              * the fgets2 call, but we need to read past the end to know.
571              */
572             bEOF = (feof(handle->fp) != 0);
573             if (!bEOF)
574             {
575                 /* Something strange happened, fgets returned NULL,
576                  * but we are not at EOF. Maybe wrong line endings?
577                  */
578                 return eCPP_UNKNOWN;
579             }
580         }
581     }
582
583     if (bEOF)
584     {
585         if (handle->parent == nullptr)
586         {
587             return eCPP_EOF;
588         }
589         cpp_close_file(handlep);
590         *handlep = handle->parent;
591         delete handle;
592         return cpp_read_line(handlep, n, buf);
593     }
594     else
595     {
596         handle->line = buf;
597         handle->line_nr++;
598     } /* Now we've read a line! */
599
600     /* Process directives if this line contains one */
601     std::string dname;
602     std::string dval;
603     if (find_directive(buf, &dname, &dval))
604     {
605         status = process_directive(handlep, dname, dval);
606         if (status != eCPP_OK)
607         {
608             return status;
609         }
610         /* Don't print lines with directives, go on to the next */
611         return cpp_read_line(handlep, n, buf);
612     }
613
614     /* Check whether we're not ifdeffed out. The order of this statement
615        is important. It has to come after #ifdef, #else and #endif, but
616        anything else should be ignored. */
617     if (is_ifdeffed_out(handle->ifdefs))
618     {
619         return cpp_read_line(handlep, n, buf);
620     }
621
622     /* Check whether we have any defines that need to be replaced. Note
623        that we have to use a best fit algorithm, rather than first come
624        first go. We do this by sorting the defines on length first, and
625        then on alphabetical order. */
626     for (t_define& define : *handle->defines)
627     {
628         if (!define.def.empty())
629         {
630             int         nn  = 0;
631             const char* ptr = buf;
632             while ((ptr = strstrw(ptr, define.name.c_str())) != nullptr)
633             {
634                 nn++;
635                 ptr += strlen(define.name.c_str());
636             }
637             if (nn > 0)
638             {
639                 // Need to erase  unmatched define in original handle
640                 gmx_cpp_t root = handle;
641                 while (root->parent != nullptr)
642                 {
643                     root = root->parent;
644                 }
645                 root->unmatched_defines.erase(define.name);
646
647                 std::string name;
648                 const char* ptr = buf;
649                 const char* ptr2;
650                 while ((ptr2 = strstrw(ptr, define.name.c_str())) != nullptr)
651                 {
652                     name.append(ptr, ptr2 - ptr);
653                     name += define.def;
654                     ptr = ptr2 + define.name.size();
655                 }
656                 name += ptr;
657                 GMX_RELEASE_ASSERT(name.size() < static_cast<size_t>(n),
658                                    "The line should fit in buf");
659                 strcpy(buf, name.c_str());
660             }
661         }
662     }
663
664     return eCPP_OK;
665 }
666
667 const char* cpp_cur_file(const gmx_cpp_t* handlep)
668 {
669     return (*handlep)->fn.c_str();
670 }
671
672 int cpp_cur_linenr(const gmx_cpp_t* handlep)
673 {
674     return (*handlep)->line_nr;
675 }
676
677 /* Close the file! Return integer status. */
678 int cpp_close_file(gmx_cpp_t* handlep)
679 {
680     gmx_cpp_t handle = *handlep;
681
682     if (!handle)
683     {
684         return eCPP_INVALID_HANDLE;
685     }
686     if (!handle->fp)
687     {
688         return eCPP_FILE_NOT_OPEN;
689     }
690     fclose(handle->fp);
691
692     if (!handle->cwd.empty())
693     {
694         gmx_chdir(handle->cwd.c_str());
695     }
696
697     handle->fp      = nullptr;
698     handle->line_nr = 0;
699     handle->line.clear();
700
701     return eCPP_OK;
702 }
703
704 const std::string* cpp_find_define(const gmx_cpp_t* handlep, const std::string& defineName)
705 {
706     for (const t_define& define : *(*handlep)->defines)
707     {
708         if (define.name == defineName)
709         {
710             return &define.def;
711         }
712     }
713
714     return nullptr;
715 }
716
717 void cpp_done(gmx_cpp_t handle)
718 {
719     int status = cpp_close_file(&handle);
720     if (status != eCPP_OK)
721     {
722         gmx_fatal(FARGS, "%s", cpp_error(&handle, status));
723     }
724     delete handle;
725 }
726
727 /* Return a string containing the error message coresponding to status
728    variable */
729 char* cpp_error(gmx_cpp_t* handlep, int status)
730 {
731     char        buf[256];
732     const char* ecpp[] = { "OK",
733                            "File not found",
734                            "End of file",
735                            "Syntax error",
736                            "Interrupted",
737                            "Invalid file handle",
738                            "Invalid delimiter for filename in #include statement",
739                            "File not open",
740                            "Unknown error, perhaps your text file uses wrong line endings?",
741                            "Error status out of range" };
742     gmx_cpp_t   handle = *handlep;
743
744     if (!handle)
745     {
746         return const_cast<char*>(ecpp[eCPP_INVALID_HANDLE]);
747     }
748
749     if ((status < 0) || (status >= eCPP_NR))
750     {
751         status = eCPP_NR;
752     }
753
754     sprintf(buf, "%s - File %s, line %d\nLast line read:\n'%s'", ecpp[status],
755             (handle && !handle->fn.empty()) ? handle->fn.c_str() : "unknown",
756             (handle) ? handle->line_nr : -1, !handle->line.empty() ? handle->line.c_str() : "");
757
758     return gmx_strdup(buf);
759 }
760
761 std::string checkAndWarnForUnusedDefines(const gmx_cpp& handle)
762 {
763     std::string warning;
764     if (!handle.unmatched_defines.empty())
765     {
766         warning =
767                 "The following macros were defined in the 'define' mdp field with the -D prefix, "
768                 "but "
769                 "were not used in the topology:\n";
770         for (auto& str : handle.unmatched_defines)
771         {
772             warning += ("    " + str + "\n");
773         }
774         warning +=
775                 "If you haven't made a spelling error, either use the macro you defined, "
776                 "or don't define the macro";
777     }
778     return warning;
779 }