Package SCons :: Package Scanner :: Module Fortran
[hide private]
[frames] | no frames]

Source Code for Module SCons.Scanner.Fortran

  1  """SCons.Scanner.Fortran 
  2   
  3  This module implements the dependency scanner for Fortran code. 
  4   
  5  """ 
  6   
  7  # 
  8  # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation 
  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   
 30  __revision__ = "src/engine/SCons/Scanner/Fortran.py 3842 2008/12/20 22:59:52 scons" 
 31   
 32  import re 
 33  import string 
 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 apply(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_contents()) 88 # retrieve all USE'd module names 89 modules = self.cre_use.findall(node.get_contents()) 90 # retrieve all defined module names 91 defmodules = self.cre_def.findall(node.get_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 = filter(lambda m, d=d: not d.has_key(m), modules) 98 #modules = self.undefinedModules(modules, defmodules) 99 100 # Convert module name to a .mod filename 101 suffix = env.subst('$FORTRANMODSUFFIX') 102 modules = map(lambda x, s=suffix: string.lower(x) + s, 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 nodes.sort() 127 nodes = map(lambda pair: pair[1], nodes) 128 return nodes
129
130 -def FortranScan(path_variable="FORTRANPATH"):
131 """Return a prototype Scanner instance for scanning source files 132 for Fortran USE & INCLUDE statements""" 133 134 # The USE statement regex matches the following: 135 # 136 # USE module_name 137 # USE :: module_name 138 # USE, INTRINSIC :: module_name 139 # USE, NON_INTRINSIC :: module_name 140 # 141 # Limitations 142 # 143 # -- While the regex can handle multiple USE statements on one line, 144 # it cannot properly handle them if they are commented out. 145 # In either of the following cases: 146 # 147 # ! USE mod_a ; USE mod_b [entire line is commented out] 148 # USE mod_a ! ; USE mod_b [in-line comment of second USE statement] 149 # 150 # the second module name (mod_b) will be picked up as a dependency 151 # even though it should be ignored. The only way I can see 152 # to rectify this would be to modify the scanner to eliminate 153 # the call to re.findall, read in the contents of the file, 154 # treating the comment character as an end-of-line character 155 # in addition to the normal linefeed, loop over each line, 156 # weeding out the comments, and looking for the USE statements. 157 # One advantage to this is that the regex passed to the scanner 158 # would no longer need to match a semicolon. 159 # 160 # -- I question whether or not we need to detect dependencies to 161 # INTRINSIC modules because these are built-in to the compiler. 162 # If we consider them a dependency, will SCons look for them, not 163 # find them, and kill the build? Or will we there be standard 164 # compiler-specific directories we will need to point to so the 165 # compiler and SCons can locate the proper object and mod files? 166 167 # Here is a breakdown of the regex: 168 # 169 # (?i) : regex is case insensitive 170 # ^ : start of line 171 # (?: : group a collection of regex symbols without saving the match as a "group" 172 # ^|; : matches either the start of the line or a semicolon - semicolon 173 # ) : end the unsaved grouping 174 # \s* : any amount of white space 175 # USE : match the string USE, case insensitive 176 # (?: : group a collection of regex symbols without saving the match as a "group" 177 # \s+| : match one or more whitespace OR .... (the next entire grouped set of regex symbols) 178 # (?: : group a collection of regex symbols without saving the match as a "group" 179 # (?: : establish another unsaved grouping of regex symbols 180 # \s* : any amount of white space 181 # , : match a comma 182 # \s* : any amount of white space 183 # (?:NON_)? : optionally match the prefix NON_, case insensitive 184 # INTRINSIC : match the string INTRINSIC, case insensitive 185 # )? : optionally match the ", INTRINSIC/NON_INTRINSIC" grouped expression 186 # \s* : any amount of white space 187 # :: : match a double colon that must appear after the INTRINSIC/NON_INTRINSIC attribute 188 # ) : end the unsaved grouping 189 # ) : end the unsaved grouping 190 # \s* : match any amount of white space 191 # (\w+) : match the module name that is being USE'd 192 # 193 # 194 use_regex = "(?i)(?:^|;)\s*USE(?:\s+|(?:(?:\s*,\s*(?:NON_)?INTRINSIC)?\s*::))\s*(\w+)" 195 196 197 # The INCLUDE statement regex matches the following: 198 # 199 # INCLUDE 'some_Text' 200 # INCLUDE "some_Text" 201 # INCLUDE "some_Text" ; INCLUDE "some_Text" 202 # INCLUDE kind_"some_Text" 203 # INCLUDE kind_'some_Text" 204 # 205 # where some_Text can include any alphanumeric and/or special character 206 # as defined by the Fortran 2003 standard. 207 # 208 # Limitations: 209 # 210 # -- The Fortran standard dictates that a " or ' in the INCLUDE'd 211 # string must be represented as a "" or '', if the quotes that wrap 212 # the entire string are either a ' or ", respectively. While the 213 # regular expression below can detect the ' or " characters just fine, 214 # the scanning logic, presently is unable to detect them and reduce 215 # them to a single instance. This probably isn't an issue since, 216 # in practice, ' or " are not generally used in filenames. 217 # 218 # -- This regex will not properly deal with multiple INCLUDE statements 219 # when the entire line has been commented out, ala 220 # 221 # ! INCLUDE 'some_file' ; INCLUDE 'some_file' 222 # 223 # In such cases, it will properly ignore the first INCLUDE file, 224 # but will actually still pick up the second. Interestingly enough, 225 # the regex will properly deal with these cases: 226 # 227 # INCLUDE 'some_file' 228 # INCLUDE 'some_file' !; INCLUDE 'some_file' 229 # 230 # To get around the above limitation, the FORTRAN programmer could 231 # simply comment each INCLUDE statement separately, like this 232 # 233 # ! INCLUDE 'some_file' !; INCLUDE 'some_file' 234 # 235 # The way I see it, the only way to get around this limitation would 236 # be to modify the scanning logic to replace the calls to re.findall 237 # with a custom loop that processes each line separately, throwing 238 # away fully commented out lines before attempting to match against 239 # the INCLUDE syntax. 240 # 241 # Here is a breakdown of the regex: 242 # 243 # (?i) : regex is case insensitive 244 # (?: : begin a non-saving group that matches the following: 245 # ^ : either the start of the line 246 # | : or 247 # ['">]\s*; : a semicolon that follows a single quote, 248 # double quote or greater than symbol (with any 249 # amount of whitespace in between). This will 250 # allow the regex to match multiple INCLUDE 251 # statements per line (although it also requires 252 # the positive lookahead assertion that is 253 # used below). It will even properly deal with 254 # (i.e. ignore) cases in which the additional 255 # INCLUDES are part of an in-line comment, ala 256 # " INCLUDE 'someFile' ! ; INCLUDE 'someFile2' " 257 # ) : end of non-saving group 258 # \s* : any amount of white space 259 # INCLUDE : match the string INCLUDE, case insensitive 260 # \s+ : match one or more white space characters 261 # (?\w+_)? : match the optional "kind-param _" prefix allowed by the standard 262 # [<"'] : match the include delimiter - an apostrophe, double quote, or less than symbol 263 # (.+?) : match one or more characters that make up 264 # the included path and file name and save it 265 # in a group. The Fortran standard allows for 266 # any non-control character to be used. The dot 267 # operator will pick up any character, including 268 # control codes, but I can't conceive of anyone 269 # putting control codes in their file names. 270 # The question mark indicates it is non-greedy so 271 # that regex will match only up to the next quote, 272 # double quote, or greater than symbol 273 # (?=["'>]) : positive lookahead assertion to match the include 274 # delimiter - an apostrophe, double quote, or 275 # greater than symbol. This level of complexity 276 # is required so that the include delimiter is 277 # not consumed by the match, thus allowing the 278 # sub-regex discussed above to uniquely match a 279 # set of semicolon-separated INCLUDE statements 280 # (as allowed by the F2003 standard) 281 282 include_regex = """(?i)(?:^|['">]\s*;)\s*INCLUDE\s+(?:\w+_)?[<"'](.+?)(?=["'>])""" 283 284 # The MODULE statement regex finds module definitions by matching 285 # the following: 286 # 287 # MODULE module_name 288 # 289 # but *not* the following: 290 # 291 # MODULE PROCEDURE procedure_name 292 # 293 # Here is a breakdown of the regex: 294 # 295 # (?i) : regex is case insensitive 296 # ^\s* : any amount of white space 297 # MODULE : match the string MODULE, case insensitive 298 # \s+ : match one or more white space characters 299 # (?!PROCEDURE) : but *don't* match if the next word matches 300 # PROCEDURE (negative lookahead assertion), 301 # case insensitive 302 # (\w+) : match one or more alphanumeric characters 303 # that make up the defined module name and 304 # save it in a group 305 306 def_regex = """(?i)^\s*MODULE\s+(?!PROCEDURE)(\w+)""" 307 308 scanner = F90Scanner("FortranScan", 309 "$FORTRANSUFFIXES", 310 path_variable, 311 use_regex, 312 include_regex, 313 def_regex) 314 return scanner
315