http://scons.tigris.org/issues/show_bug.cgi?id=2329
[scons.git] / src / engine / SCons / Scanner / Fortran.py
1 """SCons.Scanner.Fortran
2
3 This module implements the dependency scanner for Fortran code.
4
5 """
6
7 #
8 # __COPYRIGHT__
9 #
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #
29 from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
30
31 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
32
33 import re
34
35 import SCons.Node
36 import SCons.Node.FS
37 import SCons.Scanner
38 import SCons.Util
39 import SCons.Warnings
40
41 class F90Scanner(SCons.Scanner.Classic):
42     """
43     A Classic Scanner subclass for Fortran source files which takes
44     into account both USE and INCLUDE statements.  This scanner will
45     work for both F77 and F90 (and beyond) compilers.
46
47     Currently, this scanner assumes that the include files do not contain
48     USE statements.  To enable the ability to deal with USE statements
49     in include files, add logic right after the module names are found
50     to loop over each include file, search for and locate each USE
51     statement, and append each module name to the list of dependencies.
52     Caching the search results in a common dictionary somewhere so that
53     the same include file is not searched multiple times would be a
54     smart thing to do.
55     """
56
57     def __init__(self, name, suffixes, path_variable,
58                  use_regex, incl_regex, def_regex, *args, **kw):
59
60         self.cre_use = re.compile(use_regex, re.M)
61         self.cre_incl = re.compile(incl_regex, re.M)
62         self.cre_def = re.compile(def_regex, re.M)
63
64         def _scan(node, env, path, self=self):
65             node = node.rfile()
66
67             if not node.exists():
68                 return []
69
70             return self.scan(node, env, path)
71
72         kw['function'] = _scan
73         kw['path_function'] = SCons.Scanner.FindPathDirs(path_variable)
74         kw['recursive'] = 1
75         kw['skeys'] = suffixes
76         kw['name'] = name
77
78         SCons.Scanner.Current.__init__(self, *args, **kw)
79
80     def scan(self, node, env, path=()):
81
82         # cache the includes list in node so we only scan it once:
83         if node.includes != None:
84             mods_and_includes = node.includes
85         else:
86             # retrieve all included filenames
87             includes = self.cre_incl.findall(node.get_text_contents())
88             # retrieve all USE'd module names
89             modules = self.cre_use.findall(node.get_text_contents())
90             # retrieve all defined module names
91             defmodules = self.cre_def.findall(node.get_text_contents())
92
93             # Remove all USE'd module names that are defined in the same file
94             d = {}
95             for m in defmodules:
96                 d[m] = 1
97             modules = [m for m in modules if m not in d]
98             #modules = self.undefinedModules(modules, defmodules)
99
100             # Convert module name to a .mod filename
101             suffix = env.subst('$FORTRANMODSUFFIX')
102             modules = [x.lower() + suffix for x in modules]
103             # Remove unique items from the list
104             mods_and_includes = SCons.Util.unique(includes+modules)
105             node.includes = mods_and_includes
106
107         # This is a hand-coded DSU (decorate-sort-undecorate, or
108         # Schwartzian transform) pattern.  The sort key is the raw name
109         # of the file as specifed on the USE or INCLUDE line, which lets
110         # us keep the sort order constant regardless of whether the file
111         # is actually found in a Repository or locally.
112         nodes = []
113         source_dir = node.get_dir()
114         if callable(path):
115             path = path()
116         for dep in mods_and_includes:
117             n, i = self.find_include(dep, source_dir, path)
118
119             if n is None:
120                 SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
121                                     "No dependency generated for file: %s (referenced by: %s) -- file not found" % (i, node))
122             else:
123                 sortkey = self.sort_key(dep)
124                 nodes.append((sortkey, n))
125
126         return [pair[1] for pair in sorted(nodes)]
127
128 def FortranScan(path_variable="FORTRANPATH"):
129     """Return a prototype Scanner instance for scanning source files
130     for Fortran USE & INCLUDE statements"""
131
132 #   The USE statement regex matches the following:
133 #
134 #   USE module_name
135 #   USE :: module_name
136 #   USE, INTRINSIC :: module_name
137 #   USE, NON_INTRINSIC :: module_name
138 #
139 #   Limitations
140 #
141 #   --  While the regex can handle multiple USE statements on one line,
142 #       it cannot properly handle them if they are commented out.
143 #       In either of the following cases:
144 #
145 #            !  USE mod_a ; USE mod_b         [entire line is commented out]
146 #               USE mod_a ! ; USE mod_b       [in-line comment of second USE statement]
147 #
148 #       the second module name (mod_b) will be picked up as a dependency
149 #       even though it should be ignored.  The only way I can see
150 #       to rectify this would be to modify the scanner to eliminate
151 #       the call to re.findall, read in the contents of the file,
152 #       treating the comment character as an end-of-line character
153 #       in addition to the normal linefeed, loop over each line,
154 #       weeding out the comments, and looking for the USE statements.
155 #       One advantage to this is that the regex passed to the scanner
156 #       would no longer need to match a semicolon.
157 #
158 #   --  I question whether or not we need to detect dependencies to
159 #       INTRINSIC modules because these are built-in to the compiler.
160 #       If we consider them a dependency, will SCons look for them, not
161 #       find them, and kill the build?  Or will we there be standard
162 #       compiler-specific directories we will need to point to so the
163 #       compiler and SCons can locate the proper object and mod files?
164
165 #   Here is a breakdown of the regex:
166 #
167 #   (?i)               : regex is case insensitive
168 #   ^                  : start of line
169 #   (?:                : group a collection of regex symbols without saving the match as a "group"
170 #      ^|;             : matches either the start of the line or a semicolon - semicolon
171 #   )                  : end the unsaved grouping
172 #   \s*                : any amount of white space
173 #   USE                : match the string USE, case insensitive
174 #   (?:                : group a collection of regex symbols without saving the match as a "group"
175 #      \s+|            : match one or more whitespace OR ....  (the next entire grouped set of regex symbols)
176 #      (?:             : group a collection of regex symbols without saving the match as a "group"
177 #         (?:          : establish another unsaved grouping of regex symbols
178 #            \s*          : any amount of white space
179 #            ,         : match a comma
180 #            \s*       : any amount of white space
181 #            (?:NON_)? : optionally match the prefix NON_, case insensitive
182 #            INTRINSIC : match the string INTRINSIC, case insensitive
183 #         )?           : optionally match the ", INTRINSIC/NON_INTRINSIC" grouped expression
184 #         \s*          : any amount of white space
185 #         ::           : match a double colon that must appear after the INTRINSIC/NON_INTRINSIC attribute
186 #      )               : end the unsaved grouping
187 #   )                  : end the unsaved grouping
188 #   \s*                : match any amount of white space
189 #   (\w+)              : match the module name that is being USE'd
190 #
191 #
192     use_regex = "(?i)(?:^|;)\s*USE(?:\s+|(?:(?:\s*,\s*(?:NON_)?INTRINSIC)?\s*::))\s*(\w+)"
193
194
195 #   The INCLUDE statement regex matches the following:
196 #
197 #   INCLUDE 'some_Text'
198 #   INCLUDE "some_Text"
199 #   INCLUDE "some_Text" ; INCLUDE "some_Text"
200 #   INCLUDE kind_"some_Text"
201 #   INCLUDE kind_'some_Text"
202 #
203 #   where some_Text can include any alphanumeric and/or special character
204 #   as defined by the Fortran 2003 standard.
205 #
206 #   Limitations:
207 #
208 #   --  The Fortran standard dictates that a " or ' in the INCLUDE'd
209 #       string must be represented as a "" or '', if the quotes that wrap
210 #       the entire string are either a ' or ", respectively.   While the
211 #       regular expression below can detect the ' or " characters just fine,
212 #       the scanning logic, presently is unable to detect them and reduce
213 #       them to a single instance.  This probably isn't an issue since,
214 #       in practice, ' or " are not generally used in filenames.
215 #
216 #   --  This regex will not properly deal with multiple INCLUDE statements
217 #       when the entire line has been commented out, ala
218 #
219 #           ! INCLUDE 'some_file' ; INCLUDE 'some_file'
220 #
221 #       In such cases, it will properly ignore the first INCLUDE file,
222 #       but will actually still pick up the second.  Interestingly enough,
223 #       the regex will properly deal with these cases:
224 #
225 #             INCLUDE 'some_file'
226 #             INCLUDE 'some_file' !; INCLUDE 'some_file'
227 #
228 #       To get around the above limitation, the FORTRAN programmer could
229 #       simply comment each INCLUDE statement separately, like this
230 #
231 #           ! INCLUDE 'some_file' !; INCLUDE 'some_file'
232 #
233 #       The way I see it, the only way to get around this limitation would
234 #       be to modify the scanning logic to replace the calls to re.findall
235 #       with a custom loop that processes each line separately, throwing
236 #       away fully commented out lines before attempting to match against
237 #       the INCLUDE syntax.
238 #
239 #   Here is a breakdown of the regex:
240 #
241 #   (?i)               : regex is case insensitive
242 #   (?:                : begin a non-saving group that matches the following:
243 #      ^               :    either the start of the line
244 #      |               :                or
245 #      ['">]\s*;       :    a semicolon that follows a single quote,
246 #                           double quote or greater than symbol (with any
247 #                           amount of whitespace in between).  This will
248 #                           allow the regex to match multiple INCLUDE
249 #                           statements per line (although it also requires
250 #                           the positive lookahead assertion that is
251 #                           used below).  It will even properly deal with
252 #                           (i.e. ignore) cases in which the additional
253 #                           INCLUDES are part of an in-line comment, ala
254 #                                           "  INCLUDE 'someFile' ! ; INCLUDE 'someFile2' "
255 #   )                  : end of non-saving group
256 #   \s*                : any amount of white space
257 #   INCLUDE            : match the string INCLUDE, case insensitive
258 #   \s+                : match one or more white space characters
259 #   (?\w+_)?           : match the optional "kind-param _" prefix allowed by the standard
260 #   [<"']              : match the include delimiter - an apostrophe, double quote, or less than symbol
261 #   (.+?)              : match one or more characters that make up
262 #                        the included path and file name and save it
263 #                        in a group.  The Fortran standard allows for
264 #                        any non-control character to be used.  The dot
265 #                        operator will pick up any character, including
266 #                        control codes, but I can't conceive of anyone
267 #                        putting control codes in their file names.
268 #                        The question mark indicates it is non-greedy so
269 #                        that regex will match only up to the next quote,
270 #                        double quote, or greater than symbol
271 #   (?=["'>])          : positive lookahead assertion to match the include
272 #                        delimiter - an apostrophe, double quote, or
273 #                        greater than symbol.  This level of complexity
274 #                        is required so that the include delimiter is
275 #                        not consumed by the match, thus allowing the
276 #                        sub-regex discussed above to uniquely match a
277 #                        set of semicolon-separated INCLUDE statements
278 #                        (as allowed by the F2003 standard)
279
280     include_regex = """(?i)(?:^|['">]\s*;)\s*INCLUDE\s+(?:\w+_)?[<"'](.+?)(?=["'>])"""
281
282 #   The MODULE statement regex finds module definitions by matching
283 #   the following:
284 #
285 #   MODULE module_name
286 #
287 #   but *not* the following:
288 #
289 #   MODULE PROCEDURE procedure_name
290 #
291 #   Here is a breakdown of the regex:
292 #
293 #   (?i)               : regex is case insensitive
294 #   ^\s*               : any amount of white space
295 #   MODULE             : match the string MODULE, case insensitive
296 #   \s+                : match one or more white space characters
297 #   (?!PROCEDURE)      : but *don't* match if the next word matches
298 #                        PROCEDURE (negative lookahead assertion),
299 #                        case insensitive
300 #   (\w+)              : match one or more alphanumeric characters
301 #                        that make up the defined module name and
302 #                        save it in a group
303
304     def_regex = """(?i)^\s*MODULE\s+(?!PROCEDURE)(\w+)"""
305
306     scanner = F90Scanner("FortranScan",
307                          "$FORTRANSUFFIXES",
308                          path_variable,
309                          use_regex,
310                          include_regex,
311                          def_regex)
312     return scanner
313
314 # Local Variables:
315 # tab-width:4
316 # indent-tabs-mode:nil
317 # End:
318 # vim: set expandtab tabstop=4 shiftwidth=4: