def FortranScan(path_variable="FORTRANPATH"):
    """Return a prototype Scanner instance for scanning source files
    for Fortran USE & INCLUDE statements"""

#   The USE statement regex matches the following:
#
#   USE module_name
#   USE :: module_name
#   USE, INTRINSIC :: module_name
#   USE, NON_INTRINSIC :: module_name
#
#   Limitations
#
#   --  While the regex can handle multiple USE statements on one line,
#       it cannot properly handle them if they are commented out.
#       In either of the following cases:
#
#            !  USE mod_a ; USE mod_b         [entire line is commented out]
#               USE mod_a ! ; USE mod_b       [in-line comment of second USE statement]
#
#       the second module name (mod_b) will be picked up as a dependency
#       even though it should be ignored.  The only way I can see
#       to rectify this would be to modify the scanner to eliminate
#       the call to re.findall, read in the contents of the file,
#       treating the comment character as an end-of-line character
#       in addition to the normal linefeed, loop over each line,
#       weeding out the comments, and looking for the USE statements.
#       One advantage to this is that the regex passed to the scanner
#       would no longer need to match a semicolon.
#
#   --  I question whether or not we need to detect dependencies to
#       INTRINSIC modules because these are built-in to the compiler.
#       If we consider them a dependency, will SCons look for them, not
#       find them, and kill the build?  Or will we there be standard
#       compiler-specific directories we will need to point to so the
#       compiler and SCons can locate the proper object and mod files?

#   Here is a breakdown of the regex:
#
#   (?i)               : regex is case insensitive
#   ^                  : start of line
#   (?:                : group a collection of regex symbols without saving the match as a "group"
#      ^|;             : matches either the start of the line or a semicolon - semicolon
#   )                  : end the unsaved grouping
#   \s*                : any amount of white space
#   USE                : match the string USE, case insensitive
#   (?:                : group a collection of regex symbols without saving the match as a "group"
#      \s+|            : match one or more whitespace OR ....  (the next entire grouped set of regex symbols)
#      (?:             : group a collection of regex symbols without saving the match as a "group"
#         (?:          : establish another unsaved grouping of regex symbols
#            \s*          : any amount of white space
#            ,         : match a comma
#            \s*       : any amount of white space
#            (?:NON_)? : optionally match the prefix NON_, case insensitive
#            INTRINSIC : match the string INTRINSIC, case insensitive
#         )?           : optionally match the ", INTRINSIC/NON_INTRINSIC" grouped expression
#         \s*          : any amount of white space
#         ::           : match a double colon that must appear after the INTRINSIC/NON_INTRINSIC attribute
#      )               : end the unsaved grouping
#   )                  : end the unsaved grouping
#   \s*                : match any amount of white space
#   (\w+)              : match the module name that is being USE'd
#
#
    use_regex = "(?i)(?:^|;)\s*USE(?:\s+|(?:(?:\s*,\s*(?:NON_)?INTRINSIC)?\s*::))\s*(\w+)"


#   The INCLUDE statement regex matches the following:
#
#   INCLUDE 'some_Text'
#   INCLUDE "some_Text"
#   INCLUDE "some_Text" ; INCLUDE "some_Text"
#   INCLUDE kind_"some_Text"
#   INCLUDE kind_'some_Text"
#
#   where some_Text can include any alphanumeric and/or special character
#   as defined by the Fortran 2003 standard.
#
#   Limitations:
#
#   --  The Fortran standard dictates that a " or ' in the INCLUDE'd
#       string must be represented as a "" or '', if the quotes that wrap
#       the entire string are either a ' or ", respectively.   While the
#       regular expression below can detect the ' or " characters just fine,
#       the scanning logic, presently is unable to detect them and reduce
#       them to a single instance.  This probably isn't an issue since,
#       in practice, ' or " are not generally used in filenames.
#
#   --  This regex will not properly deal with multiple INCLUDE statements
#       when the entire line has been commented out, ala
#
#           ! INCLUDE 'some_file' ; INCLUDE 'some_file'
#
#       In such cases, it will properly ignore the first INCLUDE file,
#       but will actually still pick up the second.  Interestingly enough,
#       the regex will properly deal with these cases:
#
#             INCLUDE 'some_file'
#             INCLUDE 'some_file' !; INCLUDE 'some_file'
#
#       To get around the above limitation, the FORTRAN programmer could
#       simply comment each INCLUDE statement separately, like this
#
#           ! INCLUDE 'some_file' !; INCLUDE 'some_file'
#
#       The way I see it, the only way to get around this limitation would
#       be to modify the scanning logic to replace the calls to re.findall
#       with a custom loop that processes each line separately, throwing
#       away fully commented out lines before attempting to match against
#       the INCLUDE syntax.
#
#   Here is a breakdown of the regex:
#
#   (?i)               : regex is case insensitive
#   (?:                : begin a non-saving group that matches the following:
#      ^               :    either the start of the line
#      |               :                or
#      ['">]\s*;       :    a semicolon that follows a single quote,
#                           double quote or greater than symbol (with any
#                           amount of whitespace in between).  This will
#                           allow the regex to match multiple INCLUDE
#                           statements per line (although it also requires
#                           the positive lookahead assertion that is
#                           used below).  It will even properly deal with
#                           (i.e. ignore) cases in which the additional
#                           INCLUDES are part of an in-line comment, ala
#                                           "  INCLUDE 'someFile' ! ; INCLUDE 'someFile2' "
#   )                  : end of non-saving group
#   \s*                : any amount of white space
#   INCLUDE            : match the string INCLUDE, case insensitive
#   \s+                : match one or more white space characters
#   (?\w+_)?           : match the optional "kind-param _" prefix allowed by the standard
#   [<"']              : match the include delimiter - an apostrophe, double quote, or less than symbol
#   (.+?)              : match one or more characters that make up
#                        the included path and file name and save it
#                        in a group.  The Fortran standard allows for
#                        any non-control character to be used.  The dot
#                        operator will pick up any character, including
#                        control codes, but I can't conceive of anyone
#                        putting control codes in their file names.
#                        The question mark indicates it is non-greedy so
#                        that regex will match only up to the next quote,
#                        double quote, or greater than symbol
#   (?=["'>])          : positive lookahead assertion to match the include
#                        delimiter - an apostrophe, double quote, or
#                        greater than symbol.  This level of complexity
#                        is required so that the include delimiter is
#                        not consumed by the match, thus allowing the
#                        sub-regex discussed above to uniquely match a
#                        set of semicolon-separated INCLUDE statements
#                        (as allowed by the F2003 standard)

    include_regex = """(?i)(?:^|['">]\s*;)\s*INCLUDE\s+(?:\w+_)?[<"'](.+?)(?=["'>])"""

#   The MODULE statement regex finds module definitions by matching
#   the following:
#
#   MODULE module_name
#
#   but *not* the following:
#
#   MODULE PROCEDURE procedure_name
#
#   Here is a breakdown of the regex:
#
#   (?i)               : regex is case insensitive
#   ^\s*               : any amount of white space
#   MODULE             : match the string MODULE, case insensitive
#   \s+                : match one or more white space characters
#   (?!PROCEDURE)      : but *don't* match if the next word matches
#                        PROCEDURE (negative lookahead assertion),
#                        case insensitive
#   (\w+)              : match one or more alphanumeric characters
#                        that make up the defined module name and
#                        save it in a group

    def_regex = """(?i)^\s*MODULE\s+(?!PROCEDURE)(\w+)"""

    scanner = F90Scanner("FortranScan",
                         "$FORTRANSUFFIXES",
                         path_variable,
                         use_regex,
                         include_regex,
                         def_regex)
    return scanner
Return a prototype Scanner instance for scanning source files
    for Fortran USE & INCLUDE statements