aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/numpy/f2py/crackfortran.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/numpy/f2py/crackfortran.py')
-rwxr-xr-x.venv/lib/python3.12/site-packages/numpy/f2py/crackfortran.py3767
1 files changed, 3767 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/numpy/f2py/crackfortran.py b/.venv/lib/python3.12/site-packages/numpy/f2py/crackfortran.py
new file mode 100755
index 00000000..8d3fc276
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/f2py/crackfortran.py
@@ -0,0 +1,3767 @@
+#!/usr/bin/env python3
+"""
+crackfortran --- read fortran (77,90) code and extract declaration information.
+
+Copyright 1999 -- 2011 Pearu Peterson all rights reserved.
+Copyright 2011 -- present NumPy Developers.
+Permission to use, modify, and distribute this software is given under the
+terms of the NumPy License.
+
+NO WARRANTY IS EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+
+Usage of crackfortran:
+======================
+Command line keys: -quiet,-verbose,-fix,-f77,-f90,-show,-h <pyffilename>
+ -m <module name for f77 routines>,--ignore-contains
+Functions: crackfortran, crack2fortran
+The following Fortran statements/constructions are supported
+(or will be if needed):
+ block data,byte,call,character,common,complex,contains,data,
+ dimension,double complex,double precision,end,external,function,
+ implicit,integer,intent,interface,intrinsic,
+ logical,module,optional,parameter,private,public,
+ program,real,(sequence?),subroutine,type,use,virtual,
+ include,pythonmodule
+Note: 'virtual' is mapped to 'dimension'.
+Note: 'implicit integer (z) static (z)' is 'implicit static (z)' (this is minor bug).
+Note: code after 'contains' will be ignored until its scope ends.
+Note: 'common' statement is extended: dimensions are moved to variable definitions
+Note: f2py directive: <commentchar>f2py<line> is read as <line>
+Note: pythonmodule is introduced to represent Python module
+
+Usage:
+ `postlist=crackfortran(files)`
+ `postlist` contains declaration information read from the list of files `files`.
+ `crack2fortran(postlist)` returns a fortran code to be saved to pyf-file
+
+ `postlist` has the following structure:
+ *** it is a list of dictionaries containing `blocks':
+ B = {'block','body','vars','parent_block'[,'name','prefix','args','result',
+ 'implicit','externals','interfaced','common','sortvars',
+ 'commonvars','note']}
+ B['block'] = 'interface' | 'function' | 'subroutine' | 'module' |
+ 'program' | 'block data' | 'type' | 'pythonmodule' |
+ 'abstract interface'
+ B['body'] --- list containing `subblocks' with the same structure as `blocks'
+ B['parent_block'] --- dictionary of a parent block:
+ C['body'][<index>]['parent_block'] is C
+ B['vars'] --- dictionary of variable definitions
+ B['sortvars'] --- dictionary of variable definitions sorted by dependence (independent first)
+ B['name'] --- name of the block (not if B['block']=='interface')
+ B['prefix'] --- prefix string (only if B['block']=='function')
+ B['args'] --- list of argument names if B['block']== 'function' | 'subroutine'
+ B['result'] --- name of the return value (only if B['block']=='function')
+ B['implicit'] --- dictionary {'a':<variable definition>,'b':...} | None
+ B['externals'] --- list of variables being external
+ B['interfaced'] --- list of variables being external and defined
+ B['common'] --- dictionary of common blocks (list of objects)
+ B['commonvars'] --- list of variables used in common blocks (dimensions are moved to variable definitions)
+ B['from'] --- string showing the 'parents' of the current block
+ B['use'] --- dictionary of modules used in current block:
+ {<modulename>:{['only':<0|1>],['map':{<local_name1>:<use_name1>,...}]}}
+ B['note'] --- list of LaTeX comments on the block
+ B['f2pyenhancements'] --- optional dictionary
+ {'threadsafe':'','fortranname':<name>,
+ 'callstatement':<C-expr>|<multi-line block>,
+ 'callprotoargument':<C-expr-list>,
+ 'usercode':<multi-line block>|<list of multi-line blocks>,
+ 'pymethoddef:<multi-line block>'
+ }
+ B['entry'] --- dictionary {entryname:argslist,..}
+ B['varnames'] --- list of variable names given in the order of reading the
+ Fortran code, useful for derived types.
+ B['saved_interface'] --- a string of scanned routine signature, defines explicit interface
+ *** Variable definition is a dictionary
+ D = B['vars'][<variable name>] =
+ {'typespec'[,'attrspec','kindselector','charselector','=','typename']}
+ D['typespec'] = 'byte' | 'character' | 'complex' | 'double complex' |
+ 'double precision' | 'integer' | 'logical' | 'real' | 'type'
+ D['attrspec'] --- list of attributes (e.g. 'dimension(<arrayspec>)',
+ 'external','intent(in|out|inout|hide|c|callback|cache|aligned4|aligned8|aligned16)',
+ 'optional','required', etc)
+ K = D['kindselector'] = {['*','kind']} (only if D['typespec'] =
+ 'complex' | 'integer' | 'logical' | 'real' )
+ C = D['charselector'] = {['*','len','kind','f2py_len']}
+ (only if D['typespec']=='character')
+ D['='] --- initialization expression string
+ D['typename'] --- name of the type if D['typespec']=='type'
+ D['dimension'] --- list of dimension bounds
+ D['intent'] --- list of intent specifications
+ D['depend'] --- list of variable names on which current variable depends on
+ D['check'] --- list of C-expressions; if C-expr returns zero, exception is raised
+ D['note'] --- list of LaTeX comments on the variable
+ *** Meaning of kind/char selectors (few examples):
+ D['typespec>']*K['*']
+ D['typespec'](kind=K['kind'])
+ character*C['*']
+ character(len=C['len'],kind=C['kind'], f2py_len=C['f2py_len'])
+ (see also fortran type declaration statement formats below)
+
+Fortran 90 type declaration statement format (F77 is subset of F90)
+====================================================================
+(Main source: IBM XL Fortran 5.1 Language Reference Manual)
+type declaration = <typespec> [[<attrspec>]::] <entitydecl>
+<typespec> = byte |
+ character[<charselector>] |
+ complex[<kindselector>] |
+ double complex |
+ double precision |
+ integer[<kindselector>] |
+ logical[<kindselector>] |
+ real[<kindselector>] |
+ type(<typename>)
+<charselector> = * <charlen> |
+ ([len=]<len>[,[kind=]<kind>]) |
+ (kind=<kind>[,len=<len>])
+<kindselector> = * <intlen> |
+ ([kind=]<kind>)
+<attrspec> = comma separated list of attributes.
+ Only the following attributes are used in
+ building up the interface:
+ external
+ (parameter --- affects '=' key)
+ optional
+ intent
+ Other attributes are ignored.
+<intentspec> = in | out | inout
+<arrayspec> = comma separated list of dimension bounds.
+<entitydecl> = <name> [[*<charlen>][(<arrayspec>)] | [(<arrayspec>)]*<charlen>]
+ [/<init_expr>/ | =<init_expr>] [,<entitydecl>]
+
+In addition, the following attributes are used: check,depend,note
+
+TODO:
+ * Apply 'parameter' attribute (e.g. 'integer parameter :: i=2' 'real x(i)'
+ -> 'real x(2)')
+ The above may be solved by creating appropriate preprocessor program, for example.
+
+"""
+import sys
+import string
+import fileinput
+import re
+import os
+import copy
+import platform
+import codecs
+from pathlib import Path
+try:
+ import charset_normalizer
+except ImportError:
+ charset_normalizer = None
+
+from . import __version__
+
+# The environment provided by auxfuncs.py is needed for some calls to eval.
+# As the needed functions cannot be determined by static inspection of the
+# code, it is safest to use import * pending a major refactoring of f2py.
+from .auxfuncs import *
+from . import symbolic
+
+f2py_version = __version__.version
+
+# Global flags:
+strictf77 = 1 # Ignore `!' comments unless line[0]=='!'
+sourcecodeform = 'fix' # 'fix','free'
+quiet = 0 # Be verbose if 0 (Obsolete: not used any more)
+verbose = 1 # Be quiet if 0, extra verbose if > 1.
+tabchar = 4 * ' '
+pyffilename = ''
+f77modulename = ''
+skipemptyends = 0 # for old F77 programs without 'program' statement
+ignorecontains = 1
+dolowercase = 1
+debug = []
+
+# Global variables
+beginpattern = ''
+currentfilename = ''
+expectbegin = 1
+f90modulevars = {}
+filepositiontext = ''
+gotnextfile = 1
+groupcache = None
+groupcounter = 0
+grouplist = {groupcounter: []}
+groupname = ''
+include_paths = []
+neededmodule = -1
+onlyfuncs = []
+previous_context = None
+skipblocksuntil = -1
+skipfuncs = []
+skipfunctions = []
+usermodules = []
+
+
+def reset_global_f2py_vars():
+ global groupcounter, grouplist, neededmodule, expectbegin
+ global skipblocksuntil, usermodules, f90modulevars, gotnextfile
+ global filepositiontext, currentfilename, skipfunctions, skipfuncs
+ global onlyfuncs, include_paths, previous_context
+ global strictf77, sourcecodeform, quiet, verbose, tabchar, pyffilename
+ global f77modulename, skipemptyends, ignorecontains, dolowercase, debug
+
+ # flags
+ strictf77 = 1
+ sourcecodeform = 'fix'
+ quiet = 0
+ verbose = 1
+ tabchar = 4 * ' '
+ pyffilename = ''
+ f77modulename = ''
+ skipemptyends = 0
+ ignorecontains = 1
+ dolowercase = 1
+ debug = []
+ # variables
+ groupcounter = 0
+ grouplist = {groupcounter: []}
+ neededmodule = -1
+ expectbegin = 1
+ skipblocksuntil = -1
+ usermodules = []
+ f90modulevars = {}
+ gotnextfile = 1
+ filepositiontext = ''
+ currentfilename = ''
+ skipfunctions = []
+ skipfuncs = []
+ onlyfuncs = []
+ include_paths = []
+ previous_context = None
+
+
+def outmess(line, flag=1):
+ global filepositiontext
+
+ if not verbose:
+ return
+ if not quiet:
+ if flag:
+ sys.stdout.write(filepositiontext)
+ sys.stdout.write(line)
+
+re._MAXCACHE = 50
+defaultimplicitrules = {}
+for c in "abcdefghopqrstuvwxyz$_":
+ defaultimplicitrules[c] = {'typespec': 'real'}
+for c in "ijklmn":
+ defaultimplicitrules[c] = {'typespec': 'integer'}
+badnames = {}
+invbadnames = {}
+for n in ['int', 'double', 'float', 'char', 'short', 'long', 'void', 'case', 'while',
+ 'return', 'signed', 'unsigned', 'if', 'for', 'typedef', 'sizeof', 'union',
+ 'struct', 'static', 'register', 'new', 'break', 'do', 'goto', 'switch',
+ 'continue', 'else', 'inline', 'extern', 'delete', 'const', 'auto',
+ 'len', 'rank', 'shape', 'index', 'slen', 'size', '_i',
+ 'max', 'min',
+ 'flen', 'fshape',
+ 'string', 'complex_double', 'float_double', 'stdin', 'stderr', 'stdout',
+ 'type', 'default']:
+ badnames[n] = n + '_bn'
+ invbadnames[n + '_bn'] = n
+
+
+def rmbadname1(name):
+ if name in badnames:
+ errmess('rmbadname1: Replacing "%s" with "%s".\n' %
+ (name, badnames[name]))
+ return badnames[name]
+ return name
+
+
+def rmbadname(names):
+ return [rmbadname1(_m) for _m in names]
+
+
+def undo_rmbadname1(name):
+ if name in invbadnames:
+ errmess('undo_rmbadname1: Replacing "%s" with "%s".\n'
+ % (name, invbadnames[name]))
+ return invbadnames[name]
+ return name
+
+
+def undo_rmbadname(names):
+ return [undo_rmbadname1(_m) for _m in names]
+
+
+_has_f_header = re.compile(r'-\*-\s*fortran\s*-\*-', re.I).search
+_has_f90_header = re.compile(r'-\*-\s*f90\s*-\*-', re.I).search
+_has_fix_header = re.compile(r'-\*-\s*fix\s*-\*-', re.I).search
+_free_f90_start = re.compile(r'[^c*]\s*[^\s\d\t]', re.I).match
+
+# Extensions
+COMMON_FREE_EXTENSIONS = ['.f90', '.f95', '.f03', '.f08']
+COMMON_FIXED_EXTENSIONS = ['.for', '.ftn', '.f77', '.f']
+
+
+def openhook(filename, mode):
+ """Ensures that filename is opened with correct encoding parameter.
+
+ This function uses charset_normalizer package, when available, for
+ determining the encoding of the file to be opened. When charset_normalizer
+ is not available, the function detects only UTF encodings, otherwise, ASCII
+ encoding is used as fallback.
+ """
+ # Reads in the entire file. Robust detection of encoding.
+ # Correctly handles comments or late stage unicode characters
+ # gh-22871
+ if charset_normalizer is not None:
+ encoding = charset_normalizer.from_path(filename).best().encoding
+ else:
+ # hint: install charset_normalizer for correct encoding handling
+ # No need to read the whole file for trying with startswith
+ nbytes = min(32, os.path.getsize(filename))
+ with open(filename, 'rb') as fhandle:
+ raw = fhandle.read(nbytes)
+ if raw.startswith(codecs.BOM_UTF8):
+ encoding = 'UTF-8-SIG'
+ elif raw.startswith((codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE)):
+ encoding = 'UTF-32'
+ elif raw.startswith((codecs.BOM_LE, codecs.BOM_BE)):
+ encoding = 'UTF-16'
+ else:
+ # Fallback, without charset_normalizer
+ encoding = 'ascii'
+ return open(filename, mode, encoding=encoding)
+
+
+def is_free_format(fname):
+ """Check if file is in free format Fortran."""
+ # f90 allows both fixed and free format, assuming fixed unless
+ # signs of free format are detected.
+ result = False
+ if Path(fname).suffix.lower() in COMMON_FREE_EXTENSIONS:
+ result = True
+ with openhook(fname, 'r') as fhandle:
+ line = fhandle.readline()
+ n = 15 # the number of non-comment lines to scan for hints
+ if _has_f_header(line):
+ n = 0
+ elif _has_f90_header(line):
+ n = 0
+ result = True
+ while n > 0 and line:
+ if line[0] != '!' and line.strip():
+ n -= 1
+ if (line[0] != '\t' and _free_f90_start(line[:5])) or line[-2:-1] == '&':
+ result = True
+ break
+ line = fhandle.readline()
+ return result
+
+
+# Read fortran (77,90) code
+def readfortrancode(ffile, dowithline=show, istop=1):
+ """
+ Read fortran codes from files and
+ 1) Get rid of comments, line continuations, and empty lines; lower cases.
+ 2) Call dowithline(line) on every line.
+ 3) Recursively call itself when statement \"include '<filename>'\" is met.
+ """
+ global gotnextfile, filepositiontext, currentfilename, sourcecodeform, strictf77
+ global beginpattern, quiet, verbose, dolowercase, include_paths
+
+ if not istop:
+ saveglobals = gotnextfile, filepositiontext, currentfilename, sourcecodeform, strictf77,\
+ beginpattern, quiet, verbose, dolowercase
+ if ffile == []:
+ return
+ localdolowercase = dolowercase
+ # cont: set to True when the content of the last line read
+ # indicates statement continuation
+ cont = False
+ finalline = ''
+ ll = ''
+ includeline = re.compile(
+ r'\s*include\s*(\'|")(?P<name>[^\'"]*)(\'|")', re.I)
+ cont1 = re.compile(r'(?P<line>.*)&\s*\Z')
+ cont2 = re.compile(r'(\s*&|)(?P<line>.*)')
+ mline_mark = re.compile(r".*?'''")
+ if istop:
+ dowithline('', -1)
+ ll, l1 = '', ''
+ spacedigits = [' '] + [str(_m) for _m in range(10)]
+ filepositiontext = ''
+ fin = fileinput.FileInput(ffile, openhook=openhook)
+ while True:
+ try:
+ l = fin.readline()
+ except UnicodeDecodeError as msg:
+ raise Exception(
+ f'readfortrancode: reading {fin.filename()}#{fin.lineno()}'
+ f' failed with\n{msg}.\nIt is likely that installing charset_normalizer'
+ ' package will help f2py determine the input file encoding'
+ ' correctly.')
+ if not l:
+ break
+ if fin.isfirstline():
+ filepositiontext = ''
+ currentfilename = fin.filename()
+ gotnextfile = 1
+ l1 = l
+ strictf77 = 0
+ sourcecodeform = 'fix'
+ ext = os.path.splitext(currentfilename)[1]
+ if Path(currentfilename).suffix.lower() in COMMON_FIXED_EXTENSIONS and \
+ not (_has_f90_header(l) or _has_fix_header(l)):
+ strictf77 = 1
+ elif is_free_format(currentfilename) and not _has_fix_header(l):
+ sourcecodeform = 'free'
+ if strictf77:
+ beginpattern = beginpattern77
+ else:
+ beginpattern = beginpattern90
+ outmess('\tReading file %s (format:%s%s)\n'
+ % (repr(currentfilename), sourcecodeform,
+ strictf77 and ',strict' or ''))
+
+ l = l.expandtabs().replace('\xa0', ' ')
+ # Get rid of newline characters
+ while not l == '':
+ if l[-1] not in "\n\r\f":
+ break
+ l = l[:-1]
+ if not strictf77:
+ (l, rl) = split_by_unquoted(l, '!')
+ l += ' '
+ if rl[:5].lower() == '!f2py': # f2py directive
+ l, _ = split_by_unquoted(l + 4 * ' ' + rl[5:], '!')
+ if l.strip() == '': # Skip empty line
+ if sourcecodeform == 'free':
+ # In free form, a statement continues in the next line
+ # that is not a comment line [3.3.2.4^1], lines with
+ # blanks are comment lines [3.3.2.3^1]. Hence, the
+ # line continuation flag must retain its state.
+ pass
+ else:
+ # In fixed form, statement continuation is determined
+ # by a non-blank character at the 6-th position. Empty
+ # line indicates a start of a new statement
+ # [3.3.3.3^1]. Hence, the line continuation flag must
+ # be reset.
+ cont = False
+ continue
+ if sourcecodeform == 'fix':
+ if l[0] in ['*', 'c', '!', 'C', '#']:
+ if l[1:5].lower() == 'f2py': # f2py directive
+ l = ' ' + l[5:]
+ else: # Skip comment line
+ cont = False
+ continue
+ elif strictf77:
+ if len(l) > 72:
+ l = l[:72]
+ if not (l[0] in spacedigits):
+ raise Exception('readfortrancode: Found non-(space,digit) char '
+ 'in the first column.\n\tAre you sure that '
+ 'this code is in fix form?\n\tline=%s' % repr(l))
+
+ if (not cont or strictf77) and (len(l) > 5 and not l[5] == ' '):
+ # Continuation of a previous line
+ ll = ll + l[6:]
+ finalline = ''
+ origfinalline = ''
+ else:
+ if not strictf77:
+ # F90 continuation
+ r = cont1.match(l)
+ if r:
+ l = r.group('line') # Continuation follows ..
+ if cont:
+ ll = ll + cont2.match(l).group('line')
+ finalline = ''
+ origfinalline = ''
+ else:
+ # clean up line beginning from possible digits.
+ l = ' ' + l[5:]
+ if localdolowercase:
+ finalline = ll.lower()
+ else:
+ finalline = ll
+ origfinalline = ll
+ ll = l
+ cont = (r is not None)
+ else:
+ # clean up line beginning from possible digits.
+ l = ' ' + l[5:]
+ if localdolowercase:
+ finalline = ll.lower()
+ else:
+ finalline = ll
+ origfinalline = ll
+ ll = l
+
+ elif sourcecodeform == 'free':
+ if not cont and ext == '.pyf' and mline_mark.match(l):
+ l = l + '\n'
+ while True:
+ lc = fin.readline()
+ if not lc:
+ errmess(
+ 'Unexpected end of file when reading multiline\n')
+ break
+ l = l + lc
+ if mline_mark.match(lc):
+ break
+ l = l.rstrip()
+ r = cont1.match(l)
+ if r:
+ l = r.group('line') # Continuation follows ..
+ if cont:
+ ll = ll + cont2.match(l).group('line')
+ finalline = ''
+ origfinalline = ''
+ else:
+ if localdolowercase:
+ finalline = ll.lower()
+ else:
+ finalline = ll
+ origfinalline = ll
+ ll = l
+ cont = (r is not None)
+ else:
+ raise ValueError(
+ "Flag sourcecodeform must be either 'fix' or 'free': %s" % repr(sourcecodeform))
+ filepositiontext = 'Line #%d in %s:"%s"\n\t' % (
+ fin.filelineno() - 1, currentfilename, l1)
+ m = includeline.match(origfinalline)
+ if m:
+ fn = m.group('name')
+ if os.path.isfile(fn):
+ readfortrancode(fn, dowithline=dowithline, istop=0)
+ else:
+ include_dirs = [
+ os.path.dirname(currentfilename)] + include_paths
+ foundfile = 0
+ for inc_dir in include_dirs:
+ fn1 = os.path.join(inc_dir, fn)
+ if os.path.isfile(fn1):
+ foundfile = 1
+ readfortrancode(fn1, dowithline=dowithline, istop=0)
+ break
+ if not foundfile:
+ outmess('readfortrancode: could not find include file %s in %s. Ignoring.\n' % (
+ repr(fn), os.pathsep.join(include_dirs)))
+ else:
+ dowithline(finalline)
+ l1 = ll
+ if localdolowercase:
+ finalline = ll.lower()
+ else:
+ finalline = ll
+ origfinalline = ll
+ filepositiontext = 'Line #%d in %s:"%s"\n\t' % (
+ fin.filelineno() - 1, currentfilename, l1)
+ m = includeline.match(origfinalline)
+ if m:
+ fn = m.group('name')
+ if os.path.isfile(fn):
+ readfortrancode(fn, dowithline=dowithline, istop=0)
+ else:
+ include_dirs = [os.path.dirname(currentfilename)] + include_paths
+ foundfile = 0
+ for inc_dir in include_dirs:
+ fn1 = os.path.join(inc_dir, fn)
+ if os.path.isfile(fn1):
+ foundfile = 1
+ readfortrancode(fn1, dowithline=dowithline, istop=0)
+ break
+ if not foundfile:
+ outmess('readfortrancode: could not find include file %s in %s. Ignoring.\n' % (
+ repr(fn), os.pathsep.join(include_dirs)))
+ else:
+ dowithline(finalline)
+ filepositiontext = ''
+ fin.close()
+ if istop:
+ dowithline('', 1)
+ else:
+ gotnextfile, filepositiontext, currentfilename, sourcecodeform, strictf77,\
+ beginpattern, quiet, verbose, dolowercase = saveglobals
+
+# Crack line
+beforethisafter = r'\s*(?P<before>%s(?=\s*(\b(%s)\b)))' + \
+ r'\s*(?P<this>(\b(%s)\b))' + \
+ r'\s*(?P<after>%s)\s*\Z'
+##
+fortrantypes = r'character|logical|integer|real|complex|double\s*(precision\s*(complex|)|complex)|type(?=\s*\([\w\s,=(*)]*\))|byte'
+typespattern = re.compile(
+ beforethisafter % ('', fortrantypes, fortrantypes, '.*'), re.I), 'type'
+typespattern4implicit = re.compile(beforethisafter % (
+ '', fortrantypes + '|static|automatic|undefined', fortrantypes + '|static|automatic|undefined', '.*'), re.I)
+#
+functionpattern = re.compile(beforethisafter % (
+ r'([a-z]+[\w\s(=*+-/)]*?|)', 'function', 'function', '.*'), re.I), 'begin'
+subroutinepattern = re.compile(beforethisafter % (
+ r'[a-z\s]*?', 'subroutine', 'subroutine', '.*'), re.I), 'begin'
+# modulepattern=re.compile(beforethisafter%('[a-z\s]*?','module','module','.*'),re.I),'begin'
+#
+groupbegins77 = r'program|block\s*data'
+beginpattern77 = re.compile(
+ beforethisafter % ('', groupbegins77, groupbegins77, '.*'), re.I), 'begin'
+groupbegins90 = groupbegins77 + \
+ r'|module(?!\s*procedure)|python\s*module|(abstract|)\s*interface|' + \
+ r'type(?!\s*\()'
+beginpattern90 = re.compile(
+ beforethisafter % ('', groupbegins90, groupbegins90, '.*'), re.I), 'begin'
+groupends = (r'end|endprogram|endblockdata|endmodule|endpythonmodule|'
+ r'endinterface|endsubroutine|endfunction')
+endpattern = re.compile(
+ beforethisafter % ('', groupends, groupends, '.*'), re.I), 'end'
+# block, the Fortran 2008 construct needs special handling in the rest of the file
+endifs = r'end\s*(if|do|where|select|while|forall|associate|' + \
+ r'critical|enum|team)'
+endifpattern = re.compile(
+ beforethisafter % (r'[\w]*?', endifs, endifs, '.*'), re.I), 'endif'
+#
+moduleprocedures = r'module\s*procedure'
+moduleprocedurepattern = re.compile(
+ beforethisafter % ('', moduleprocedures, moduleprocedures, '.*'), re.I), \
+ 'moduleprocedure'
+implicitpattern = re.compile(
+ beforethisafter % ('', 'implicit', 'implicit', '.*'), re.I), 'implicit'
+dimensionpattern = re.compile(beforethisafter % (
+ '', 'dimension|virtual', 'dimension|virtual', '.*'), re.I), 'dimension'
+externalpattern = re.compile(
+ beforethisafter % ('', 'external', 'external', '.*'), re.I), 'external'
+optionalpattern = re.compile(
+ beforethisafter % ('', 'optional', 'optional', '.*'), re.I), 'optional'
+requiredpattern = re.compile(
+ beforethisafter % ('', 'required', 'required', '.*'), re.I), 'required'
+publicpattern = re.compile(
+ beforethisafter % ('', 'public', 'public', '.*'), re.I), 'public'
+privatepattern = re.compile(
+ beforethisafter % ('', 'private', 'private', '.*'), re.I), 'private'
+intrinsicpattern = re.compile(
+ beforethisafter % ('', 'intrinsic', 'intrinsic', '.*'), re.I), 'intrinsic'
+intentpattern = re.compile(beforethisafter % (
+ '', 'intent|depend|note|check', 'intent|depend|note|check', r'\s*\(.*?\).*'), re.I), 'intent'
+parameterpattern = re.compile(
+ beforethisafter % ('', 'parameter', 'parameter', r'\s*\(.*'), re.I), 'parameter'
+datapattern = re.compile(
+ beforethisafter % ('', 'data', 'data', '.*'), re.I), 'data'
+callpattern = re.compile(
+ beforethisafter % ('', 'call', 'call', '.*'), re.I), 'call'
+entrypattern = re.compile(
+ beforethisafter % ('', 'entry', 'entry', '.*'), re.I), 'entry'
+callfunpattern = re.compile(
+ beforethisafter % ('', 'callfun', 'callfun', '.*'), re.I), 'callfun'
+commonpattern = re.compile(
+ beforethisafter % ('', 'common', 'common', '.*'), re.I), 'common'
+usepattern = re.compile(
+ beforethisafter % ('', 'use', 'use', '.*'), re.I), 'use'
+containspattern = re.compile(
+ beforethisafter % ('', 'contains', 'contains', ''), re.I), 'contains'
+formatpattern = re.compile(
+ beforethisafter % ('', 'format', 'format', '.*'), re.I), 'format'
+# Non-fortran and f2py-specific statements
+f2pyenhancementspattern = re.compile(beforethisafter % ('', 'threadsafe|fortranname|callstatement|callprotoargument|usercode|pymethoddef',
+ 'threadsafe|fortranname|callstatement|callprotoargument|usercode|pymethoddef', '.*'), re.I | re.S), 'f2pyenhancements'
+multilinepattern = re.compile(
+ r"\s*(?P<before>''')(?P<this>.*?)(?P<after>''')\s*\Z", re.S), 'multiline'
+##
+
+def split_by_unquoted(line, characters):
+ """
+ Splits the line into (line[:i], line[i:]),
+ where i is the index of first occurrence of one of the characters
+ not within quotes, or len(line) if no such index exists
+ """
+ assert not (set('"\'') & set(characters)), "cannot split by unquoted quotes"
+ r = re.compile(
+ r"\A(?P<before>({single_quoted}|{double_quoted}|{not_quoted})*)"
+ r"(?P<after>{char}.*)\Z".format(
+ not_quoted="[^\"'{}]".format(re.escape(characters)),
+ char="[{}]".format(re.escape(characters)),
+ single_quoted=r"('([^'\\]|(\\.))*')",
+ double_quoted=r'("([^"\\]|(\\.))*")'))
+ m = r.match(line)
+ if m:
+ d = m.groupdict()
+ return (d["before"], d["after"])
+ return (line, "")
+
+def _simplifyargs(argsline):
+ a = []
+ for n in markoutercomma(argsline).split('@,@'):
+ for r in '(),':
+ n = n.replace(r, '_')
+ a.append(n)
+ return ','.join(a)
+
+crackline_re_1 = re.compile(r'\s*(?P<result>\b[a-z]+\w*\b)\s*=.*', re.I)
+crackline_bind_1 = re.compile(r'\s*(?P<bind>\b[a-z]+\w*\b)\s*=.*', re.I)
+crackline_bindlang = re.compile(r'\s*bind\(\s*(?P<lang>[^,]+)\s*,\s*name\s*=\s*"(?P<lang_name>[^"]+)"\s*\)', re.I)
+
+def crackline(line, reset=0):
+ """
+ reset=-1 --- initialize
+ reset=0 --- crack the line
+ reset=1 --- final check if mismatch of blocks occurred
+
+ Cracked data is saved in grouplist[0].
+ """
+ global beginpattern, groupcounter, groupname, groupcache, grouplist
+ global filepositiontext, currentfilename, neededmodule, expectbegin
+ global skipblocksuntil, skipemptyends, previous_context, gotnextfile
+
+ _, has_semicolon = split_by_unquoted(line, ";")
+ if has_semicolon and not (f2pyenhancementspattern[0].match(line) or
+ multilinepattern[0].match(line)):
+ # XXX: non-zero reset values need testing
+ assert reset == 0, repr(reset)
+ # split line on unquoted semicolons
+ line, semicolon_line = split_by_unquoted(line, ";")
+ while semicolon_line:
+ crackline(line, reset)
+ line, semicolon_line = split_by_unquoted(semicolon_line[1:], ";")
+ crackline(line, reset)
+ return
+ if reset < 0:
+ groupcounter = 0
+ groupname = {groupcounter: ''}
+ groupcache = {groupcounter: {}}
+ grouplist = {groupcounter: []}
+ groupcache[groupcounter]['body'] = []
+ groupcache[groupcounter]['vars'] = {}
+ groupcache[groupcounter]['block'] = ''
+ groupcache[groupcounter]['name'] = ''
+ neededmodule = -1
+ skipblocksuntil = -1
+ return
+ if reset > 0:
+ fl = 0
+ if f77modulename and neededmodule == groupcounter:
+ fl = 2
+ while groupcounter > fl:
+ outmess('crackline: groupcounter=%s groupname=%s\n' %
+ (repr(groupcounter), repr(groupname)))
+ outmess(
+ 'crackline: Mismatch of blocks encountered. Trying to fix it by assuming "end" statement.\n')
+ grouplist[groupcounter - 1].append(groupcache[groupcounter])
+ grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
+ del grouplist[groupcounter]
+ groupcounter = groupcounter - 1
+ if f77modulename and neededmodule == groupcounter:
+ grouplist[groupcounter - 1].append(groupcache[groupcounter])
+ grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
+ del grouplist[groupcounter]
+ groupcounter = groupcounter - 1 # end interface
+ grouplist[groupcounter - 1].append(groupcache[groupcounter])
+ grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
+ del grouplist[groupcounter]
+ groupcounter = groupcounter - 1 # end module
+ neededmodule = -1
+ return
+ if line == '':
+ return
+ flag = 0
+ for pat in [dimensionpattern, externalpattern, intentpattern, optionalpattern,
+ requiredpattern,
+ parameterpattern, datapattern, publicpattern, privatepattern,
+ intrinsicpattern,
+ endifpattern, endpattern,
+ formatpattern,
+ beginpattern, functionpattern, subroutinepattern,
+ implicitpattern, typespattern, commonpattern,
+ callpattern, usepattern, containspattern,
+ entrypattern,
+ f2pyenhancementspattern,
+ multilinepattern,
+ moduleprocedurepattern
+ ]:
+ m = pat[0].match(line)
+ if m:
+ break
+ flag = flag + 1
+ if not m:
+ re_1 = crackline_re_1
+ if 0 <= skipblocksuntil <= groupcounter:
+ return
+ if 'externals' in groupcache[groupcounter]:
+ for name in groupcache[groupcounter]['externals']:
+ if name in invbadnames:
+ name = invbadnames[name]
+ if 'interfaced' in groupcache[groupcounter] and name in groupcache[groupcounter]['interfaced']:
+ continue
+ m1 = re.match(
+ r'(?P<before>[^"]*)\b%s\b\s*@\(@(?P<args>[^@]*)@\)@.*\Z' % name, markouterparen(line), re.I)
+ if m1:
+ m2 = re_1.match(m1.group('before'))
+ a = _simplifyargs(m1.group('args'))
+ if m2:
+ line = 'callfun %s(%s) result (%s)' % (
+ name, a, m2.group('result'))
+ else:
+ line = 'callfun %s(%s)' % (name, a)
+ m = callfunpattern[0].match(line)
+ if not m:
+ outmess(
+ 'crackline: could not resolve function call for line=%s.\n' % repr(line))
+ return
+ analyzeline(m, 'callfun', line)
+ return
+ if verbose > 1 or (verbose == 1 and currentfilename.lower().endswith('.pyf')):
+ previous_context = None
+ outmess('crackline:%d: No pattern for line\n' % (groupcounter))
+ return
+ elif pat[1] == 'end':
+ if 0 <= skipblocksuntil < groupcounter:
+ groupcounter = groupcounter - 1
+ if skipblocksuntil <= groupcounter:
+ return
+ if groupcounter <= 0:
+ raise Exception('crackline: groupcounter(=%s) is nonpositive. '
+ 'Check the blocks.'
+ % (groupcounter))
+ m1 = beginpattern[0].match((line))
+ if (m1) and (not m1.group('this') == groupname[groupcounter]):
+ raise Exception('crackline: End group %s does not match with '
+ 'previous Begin group %s\n\t%s' %
+ (repr(m1.group('this')), repr(groupname[groupcounter]),
+ filepositiontext)
+ )
+ if skipblocksuntil == groupcounter:
+ skipblocksuntil = -1
+ grouplist[groupcounter - 1].append(groupcache[groupcounter])
+ grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
+ del grouplist[groupcounter]
+ groupcounter = groupcounter - 1
+ if not skipemptyends:
+ expectbegin = 1
+ elif pat[1] == 'begin':
+ if 0 <= skipblocksuntil <= groupcounter:
+ groupcounter = groupcounter + 1
+ return
+ gotnextfile = 0
+ analyzeline(m, pat[1], line)
+ expectbegin = 0
+ elif pat[1] == 'endif':
+ pass
+ elif pat[1] == 'moduleprocedure':
+ analyzeline(m, pat[1], line)
+ elif pat[1] == 'contains':
+ if ignorecontains:
+ return
+ if 0 <= skipblocksuntil <= groupcounter:
+ return
+ skipblocksuntil = groupcounter
+ else:
+ if 0 <= skipblocksuntil <= groupcounter:
+ return
+ analyzeline(m, pat[1], line)
+
+
+def markouterparen(line):
+ l = ''
+ f = 0
+ for c in line:
+ if c == '(':
+ f = f + 1
+ if f == 1:
+ l = l + '@(@'
+ continue
+ elif c == ')':
+ f = f - 1
+ if f == 0:
+ l = l + '@)@'
+ continue
+ l = l + c
+ return l
+
+
+def markoutercomma(line, comma=','):
+ l = ''
+ f = 0
+ before, after = split_by_unquoted(line, comma + '()')
+ l += before
+ while after:
+ if (after[0] == comma) and (f == 0):
+ l += '@' + comma + '@'
+ else:
+ l += after[0]
+ if after[0] == '(':
+ f += 1
+ elif after[0] == ')':
+ f -= 1
+ before, after = split_by_unquoted(after[1:], comma + '()')
+ l += before
+ assert not f, repr((f, line, l))
+ return l
+
+def unmarkouterparen(line):
+ r = line.replace('@(@', '(').replace('@)@', ')')
+ return r
+
+
+def appenddecl(decl, decl2, force=1):
+ if not decl:
+ decl = {}
+ if not decl2:
+ return decl
+ if decl is decl2:
+ return decl
+ for k in list(decl2.keys()):
+ if k == 'typespec':
+ if force or k not in decl:
+ decl[k] = decl2[k]
+ elif k == 'attrspec':
+ for l in decl2[k]:
+ decl = setattrspec(decl, l, force)
+ elif k == 'kindselector':
+ decl = setkindselector(decl, decl2[k], force)
+ elif k == 'charselector':
+ decl = setcharselector(decl, decl2[k], force)
+ elif k in ['=', 'typename']:
+ if force or k not in decl:
+ decl[k] = decl2[k]
+ elif k == 'note':
+ pass
+ elif k in ['intent', 'check', 'dimension', 'optional',
+ 'required', 'depend']:
+ errmess('appenddecl: "%s" not implemented.\n' % k)
+ else:
+ raise Exception('appenddecl: Unknown variable definition key: ' +
+ str(k))
+ return decl
+
+selectpattern = re.compile(
+ r'\s*(?P<this>(@\(@.*?@\)@|\*[\d*]+|\*\s*@\(@.*?@\)@|))(?P<after>.*)\Z', re.I)
+typedefpattern = re.compile(
+ r'(?:,(?P<attributes>[\w(),]+))?(::)?(?P<name>\b[a-z$_][\w$]*\b)'
+ r'(?:\((?P<params>[\w,]*)\))?\Z', re.I)
+nameargspattern = re.compile(
+ r'\s*(?P<name>\b[\w$]+\b)\s*(@\(@\s*(?P<args>[\w\s,]*)\s*@\)@|)\s*((result(\s*@\(@\s*(?P<result>\b[\w$]+\b)\s*@\)@|))|(bind\s*@\(@\s*(?P<bind>(?:(?!@\)@).)*)\s*@\)@))*\s*\Z', re.I)
+operatorpattern = re.compile(
+ r'\s*(?P<scheme>(operator|assignment))'
+ r'@\(@\s*(?P<name>[^)]+)\s*@\)@\s*\Z', re.I)
+callnameargspattern = re.compile(
+ r'\s*(?P<name>\b[\w$]+\b)\s*@\(@\s*(?P<args>.*)\s*@\)@\s*\Z', re.I)
+real16pattern = re.compile(
+ r'([-+]?(?:\d+(?:\.\d*)?|\d*\.\d+))[dD]((?:[-+]?\d+)?)')
+real8pattern = re.compile(
+ r'([-+]?((?:\d+(?:\.\d*)?|\d*\.\d+))[eE]((?:[-+]?\d+)?)|(\d+\.\d*))')
+
+_intentcallbackpattern = re.compile(r'intent\s*\(.*?\bcallback\b', re.I)
+
+
+def _is_intent_callback(vdecl):
+ for a in vdecl.get('attrspec', []):
+ if _intentcallbackpattern.match(a):
+ return 1
+ return 0
+
+
+def _resolvetypedefpattern(line):
+ line = ''.join(line.split()) # removes whitespace
+ m1 = typedefpattern.match(line)
+ print(line, m1)
+ if m1:
+ attrs = m1.group('attributes')
+ attrs = [a.lower() for a in attrs.split(',')] if attrs else []
+ return m1.group('name'), attrs, m1.group('params')
+ return None, [], None
+
+def parse_name_for_bind(line):
+ pattern = re.compile(r'bind\(\s*(?P<lang>[^,]+)(?:\s*,\s*name\s*=\s*["\'](?P<name>[^"\']+)["\']\s*)?\)', re.I)
+ match = pattern.search(line)
+ bind_statement = None
+ if match:
+ bind_statement = match.group(0)
+ # Remove the 'bind' construct from the line.
+ line = line[:match.start()] + line[match.end():]
+ return line, bind_statement
+
+def _resolvenameargspattern(line):
+ line, bind_cname = parse_name_for_bind(line)
+ line = markouterparen(line)
+ m1 = nameargspattern.match(line)
+ if m1:
+ return m1.group('name'), m1.group('args'), m1.group('result'), bind_cname
+ m1 = operatorpattern.match(line)
+ if m1:
+ name = m1.group('scheme') + '(' + m1.group('name') + ')'
+ return name, [], None, None
+ m1 = callnameargspattern.match(line)
+ if m1:
+ return m1.group('name'), m1.group('args'), None, None
+ return None, [], None, None
+
+
+def analyzeline(m, case, line):
+ """
+ Reads each line in the input file in sequence and updates global vars.
+
+ Effectively reads and collects information from the input file to the
+ global variable groupcache, a dictionary containing info about each part
+ of the fortran module.
+
+ At the end of analyzeline, information is filtered into the correct dict
+ keys, but parameter values and dimensions are not yet interpreted.
+ """
+ global groupcounter, groupname, groupcache, grouplist, filepositiontext
+ global currentfilename, f77modulename, neededinterface, neededmodule
+ global expectbegin, gotnextfile, previous_context
+
+ block = m.group('this')
+ if case != 'multiline':
+ previous_context = None
+ if expectbegin and case not in ['begin', 'call', 'callfun', 'type'] \
+ and not skipemptyends and groupcounter < 1:
+ newname = os.path.basename(currentfilename).split('.')[0]
+ outmess(
+ 'analyzeline: no group yet. Creating program group with name "%s".\n' % newname)
+ gotnextfile = 0
+ groupcounter = groupcounter + 1
+ groupname[groupcounter] = 'program'
+ groupcache[groupcounter] = {}
+ grouplist[groupcounter] = []
+ groupcache[groupcounter]['body'] = []
+ groupcache[groupcounter]['vars'] = {}
+ groupcache[groupcounter]['block'] = 'program'
+ groupcache[groupcounter]['name'] = newname
+ groupcache[groupcounter]['from'] = 'fromsky'
+ expectbegin = 0
+ if case in ['begin', 'call', 'callfun']:
+ # Crack line => block,name,args,result
+ block = block.lower()
+ if re.match(r'block\s*data', block, re.I):
+ block = 'block data'
+ elif re.match(r'python\s*module', block, re.I):
+ block = 'python module'
+ elif re.match(r'abstract\s*interface', block, re.I):
+ block = 'abstract interface'
+ if block == 'type':
+ name, attrs, _ = _resolvetypedefpattern(m.group('after'))
+ groupcache[groupcounter]['vars'][name] = dict(attrspec = attrs)
+ args = []
+ result = None
+ else:
+ name, args, result, bindcline = _resolvenameargspattern(m.group('after'))
+ if name is None:
+ if block == 'block data':
+ name = '_BLOCK_DATA_'
+ else:
+ name = ''
+ if block not in ['interface', 'block data', 'abstract interface']:
+ outmess('analyzeline: No name/args pattern found for line.\n')
+
+ previous_context = (block, name, groupcounter)
+ if args:
+ args = rmbadname([x.strip()
+ for x in markoutercomma(args).split('@,@')])
+ else:
+ args = []
+ if '' in args:
+ while '' in args:
+ args.remove('')
+ outmess(
+ 'analyzeline: argument list is malformed (missing argument).\n')
+
+ # end of crack line => block,name,args,result
+ needmodule = 0
+ needinterface = 0
+
+ if case in ['call', 'callfun']:
+ needinterface = 1
+ if 'args' not in groupcache[groupcounter]:
+ return
+ if name not in groupcache[groupcounter]['args']:
+ return
+ for it in grouplist[groupcounter]:
+ if it['name'] == name:
+ return
+ if name in groupcache[groupcounter]['interfaced']:
+ return
+ block = {'call': 'subroutine', 'callfun': 'function'}[case]
+ if f77modulename and neededmodule == -1 and groupcounter <= 1:
+ neededmodule = groupcounter + 2
+ needmodule = 1
+ if block not in ['interface', 'abstract interface']:
+ needinterface = 1
+ # Create new block(s)
+ groupcounter = groupcounter + 1
+ groupcache[groupcounter] = {}
+ grouplist[groupcounter] = []
+ if needmodule:
+ if verbose > 1:
+ outmess('analyzeline: Creating module block %s\n' %
+ repr(f77modulename), 0)
+ groupname[groupcounter] = 'module'
+ groupcache[groupcounter]['block'] = 'python module'
+ groupcache[groupcounter]['name'] = f77modulename
+ groupcache[groupcounter]['from'] = ''
+ groupcache[groupcounter]['body'] = []
+ groupcache[groupcounter]['externals'] = []
+ groupcache[groupcounter]['interfaced'] = []
+ groupcache[groupcounter]['vars'] = {}
+ groupcounter = groupcounter + 1
+ groupcache[groupcounter] = {}
+ grouplist[groupcounter] = []
+ if needinterface:
+ if verbose > 1:
+ outmess('analyzeline: Creating additional interface block (groupcounter=%s).\n' % (
+ groupcounter), 0)
+ groupname[groupcounter] = 'interface'
+ groupcache[groupcounter]['block'] = 'interface'
+ groupcache[groupcounter]['name'] = 'unknown_interface'
+ groupcache[groupcounter]['from'] = '%s:%s' % (
+ groupcache[groupcounter - 1]['from'], groupcache[groupcounter - 1]['name'])
+ groupcache[groupcounter]['body'] = []
+ groupcache[groupcounter]['externals'] = []
+ groupcache[groupcounter]['interfaced'] = []
+ groupcache[groupcounter]['vars'] = {}
+ groupcounter = groupcounter + 1
+ groupcache[groupcounter] = {}
+ grouplist[groupcounter] = []
+ groupname[groupcounter] = block
+ groupcache[groupcounter]['block'] = block
+ if not name:
+ name = 'unknown_' + block.replace(' ', '_')
+ groupcache[groupcounter]['prefix'] = m.group('before')
+ groupcache[groupcounter]['name'] = rmbadname1(name)
+ groupcache[groupcounter]['result'] = result
+ if groupcounter == 1:
+ groupcache[groupcounter]['from'] = currentfilename
+ else:
+ if f77modulename and groupcounter == 3:
+ groupcache[groupcounter]['from'] = '%s:%s' % (
+ groupcache[groupcounter - 1]['from'], currentfilename)
+ else:
+ groupcache[groupcounter]['from'] = '%s:%s' % (
+ groupcache[groupcounter - 1]['from'], groupcache[groupcounter - 1]['name'])
+ for k in list(groupcache[groupcounter].keys()):
+ if not groupcache[groupcounter][k]:
+ del groupcache[groupcounter][k]
+
+ groupcache[groupcounter]['args'] = args
+ groupcache[groupcounter]['body'] = []
+ groupcache[groupcounter]['externals'] = []
+ groupcache[groupcounter]['interfaced'] = []
+ groupcache[groupcounter]['vars'] = {}
+ groupcache[groupcounter]['entry'] = {}
+ # end of creation
+ if block == 'type':
+ groupcache[groupcounter]['varnames'] = []
+
+ if case in ['call', 'callfun']: # set parents variables
+ if name not in groupcache[groupcounter - 2]['externals']:
+ groupcache[groupcounter - 2]['externals'].append(name)
+ groupcache[groupcounter]['vars'] = copy.deepcopy(
+ groupcache[groupcounter - 2]['vars'])
+ try:
+ del groupcache[groupcounter]['vars'][name][
+ groupcache[groupcounter]['vars'][name]['attrspec'].index('external')]
+ except Exception:
+ pass
+ if block in ['function', 'subroutine']: # set global attributes
+ # name is fortran name
+ if bindcline:
+ bindcdat = re.search(crackline_bindlang, bindcline)
+ if bindcdat:
+ groupcache[groupcounter]['bindlang'] = {name : {}}
+ groupcache[groupcounter]['bindlang'][name]["lang"] = bindcdat.group('lang')
+ if bindcdat.group('lang_name'):
+ groupcache[groupcounter]['bindlang'][name]["name"] = bindcdat.group('lang_name')
+ try:
+ groupcache[groupcounter]['vars'][name] = appenddecl(
+ groupcache[groupcounter]['vars'][name], groupcache[groupcounter - 2]['vars'][''])
+ except Exception:
+ pass
+ if case == 'callfun': # return type
+ if result and result in groupcache[groupcounter]['vars']:
+ if not name == result:
+ groupcache[groupcounter]['vars'][name] = appenddecl(
+ groupcache[groupcounter]['vars'][name], groupcache[groupcounter]['vars'][result])
+ # if groupcounter>1: # name is interfaced
+ try:
+ groupcache[groupcounter - 2]['interfaced'].append(name)
+ except Exception:
+ pass
+ if block == 'function':
+ t = typespattern[0].match(m.group('before') + ' ' + name)
+ if t:
+ typespec, selector, attr, edecl = cracktypespec0(
+ t.group('this'), t.group('after'))
+ updatevars(typespec, selector, attr, edecl)
+
+ if case in ['call', 'callfun']:
+ grouplist[groupcounter - 1].append(groupcache[groupcounter])
+ grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
+ del grouplist[groupcounter]
+ groupcounter = groupcounter - 1 # end routine
+ grouplist[groupcounter - 1].append(groupcache[groupcounter])
+ grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
+ del grouplist[groupcounter]
+ groupcounter = groupcounter - 1 # end interface
+
+ elif case == 'entry':
+ name, args, result, _= _resolvenameargspattern(m.group('after'))
+ if name is not None:
+ if args:
+ args = rmbadname([x.strip()
+ for x in markoutercomma(args).split('@,@')])
+ else:
+ args = []
+ assert result is None, repr(result)
+ groupcache[groupcounter]['entry'][name] = args
+ previous_context = ('entry', name, groupcounter)
+ elif case == 'type':
+ typespec, selector, attr, edecl = cracktypespec0(
+ block, m.group('after'))
+ last_name = updatevars(typespec, selector, attr, edecl)
+ if last_name is not None:
+ previous_context = ('variable', last_name, groupcounter)
+ elif case in ['dimension', 'intent', 'optional', 'required', 'external', 'public', 'private', 'intrinsic']:
+ edecl = groupcache[groupcounter]['vars']
+ ll = m.group('after').strip()
+ i = ll.find('::')
+ if i < 0 and case == 'intent':
+ i = markouterparen(ll).find('@)@') - 2
+ ll = ll[:i + 1] + '::' + ll[i + 1:]
+ i = ll.find('::')
+ if ll[i:] == '::' and 'args' in groupcache[groupcounter]:
+ outmess('All arguments will have attribute %s%s\n' %
+ (m.group('this'), ll[:i]))
+ ll = ll + ','.join(groupcache[groupcounter]['args'])
+ if i < 0:
+ i = 0
+ pl = ''
+ else:
+ pl = ll[:i].strip()
+ ll = ll[i + 2:]
+ ch = markoutercomma(pl).split('@,@')
+ if len(ch) > 1:
+ pl = ch[0]
+ outmess('analyzeline: cannot handle multiple attributes without type specification. Ignoring %r.\n' % (
+ ','.join(ch[1:])))
+ last_name = None
+
+ for e in [x.strip() for x in markoutercomma(ll).split('@,@')]:
+ m1 = namepattern.match(e)
+ if not m1:
+ if case in ['public', 'private']:
+ k = ''
+ else:
+ print(m.groupdict())
+ outmess('analyzeline: no name pattern found in %s statement for %s. Skipping.\n' % (
+ case, repr(e)))
+ continue
+ else:
+ k = rmbadname1(m1.group('name'))
+ if case in ['public', 'private'] and \
+ (k == 'operator' or k == 'assignment'):
+ k += m1.group('after')
+ if k not in edecl:
+ edecl[k] = {}
+ if case == 'dimension':
+ ap = case + m1.group('after')
+ if case == 'intent':
+ ap = m.group('this') + pl
+ if _intentcallbackpattern.match(ap):
+ if k not in groupcache[groupcounter]['args']:
+ if groupcounter > 1:
+ if '__user__' not in groupcache[groupcounter - 2]['name']:
+ outmess(
+ 'analyzeline: missing __user__ module (could be nothing)\n')
+ # fixes ticket 1693
+ if k != groupcache[groupcounter]['name']:
+ outmess('analyzeline: appending intent(callback) %s'
+ ' to %s arguments\n' % (k, groupcache[groupcounter]['name']))
+ groupcache[groupcounter]['args'].append(k)
+ else:
+ errmess(
+ 'analyzeline: intent(callback) %s is ignored\n' % (k))
+ else:
+ errmess('analyzeline: intent(callback) %s is already'
+ ' in argument list\n' % (k))
+ if case in ['optional', 'required', 'public', 'external', 'private', 'intrinsic']:
+ ap = case
+ if 'attrspec' in edecl[k]:
+ edecl[k]['attrspec'].append(ap)
+ else:
+ edecl[k]['attrspec'] = [ap]
+ if case == 'external':
+ if groupcache[groupcounter]['block'] == 'program':
+ outmess('analyzeline: ignoring program arguments\n')
+ continue
+ if k not in groupcache[groupcounter]['args']:
+ continue
+ if 'externals' not in groupcache[groupcounter]:
+ groupcache[groupcounter]['externals'] = []
+ groupcache[groupcounter]['externals'].append(k)
+ last_name = k
+ groupcache[groupcounter]['vars'] = edecl
+ if last_name is not None:
+ previous_context = ('variable', last_name, groupcounter)
+ elif case == 'moduleprocedure':
+ groupcache[groupcounter]['implementedby'] = \
+ [x.strip() for x in m.group('after').split(',')]
+ elif case == 'parameter':
+ edecl = groupcache[groupcounter]['vars']
+ ll = m.group('after').strip()[1:-1]
+ last_name = None
+ for e in markoutercomma(ll).split('@,@'):
+ try:
+ k, initexpr = [x.strip() for x in e.split('=')]
+ except Exception:
+ outmess(
+ 'analyzeline: could not extract name,expr in parameter statement "%s" of "%s"\n' % (e, ll))
+ continue
+ params = get_parameters(edecl)
+ k = rmbadname1(k)
+ if k not in edecl:
+ edecl[k] = {}
+ if '=' in edecl[k] and (not edecl[k]['='] == initexpr):
+ outmess('analyzeline: Overwriting the value of parameter "%s" ("%s") with "%s".\n' % (
+ k, edecl[k]['='], initexpr))
+ t = determineexprtype(initexpr, params)
+ if t:
+ if t.get('typespec') == 'real':
+ tt = list(initexpr)
+ for m in real16pattern.finditer(initexpr):
+ tt[m.start():m.end()] = list(
+ initexpr[m.start():m.end()].lower().replace('d', 'e'))
+ initexpr = ''.join(tt)
+ elif t.get('typespec') == 'complex':
+ initexpr = initexpr[1:].lower().replace('d', 'e').\
+ replace(',', '+1j*(')
+ try:
+ v = eval(initexpr, {}, params)
+ except (SyntaxError, NameError, TypeError) as msg:
+ errmess('analyzeline: Failed to evaluate %r. Ignoring: %s\n'
+ % (initexpr, msg))
+ continue
+ edecl[k]['='] = repr(v)
+ if 'attrspec' in edecl[k]:
+ edecl[k]['attrspec'].append('parameter')
+ else:
+ edecl[k]['attrspec'] = ['parameter']
+ last_name = k
+ groupcache[groupcounter]['vars'] = edecl
+ if last_name is not None:
+ previous_context = ('variable', last_name, groupcounter)
+ elif case == 'implicit':
+ if m.group('after').strip().lower() == 'none':
+ groupcache[groupcounter]['implicit'] = None
+ elif m.group('after'):
+ if 'implicit' in groupcache[groupcounter]:
+ impl = groupcache[groupcounter]['implicit']
+ else:
+ impl = {}
+ if impl is None:
+ outmess(
+ 'analyzeline: Overwriting earlier "implicit none" statement.\n')
+ impl = {}
+ for e in markoutercomma(m.group('after')).split('@,@'):
+ decl = {}
+ m1 = re.match(
+ r'\s*(?P<this>.*?)\s*(\(\s*(?P<after>[a-z-, ]+)\s*\)\s*|)\Z', e, re.I)
+ if not m1:
+ outmess(
+ 'analyzeline: could not extract info of implicit statement part "%s"\n' % (e))
+ continue
+ m2 = typespattern4implicit.match(m1.group('this'))
+ if not m2:
+ outmess(
+ 'analyzeline: could not extract types pattern of implicit statement part "%s"\n' % (e))
+ continue
+ typespec, selector, attr, edecl = cracktypespec0(
+ m2.group('this'), m2.group('after'))
+ kindselect, charselect, typename = cracktypespec(
+ typespec, selector)
+ decl['typespec'] = typespec
+ decl['kindselector'] = kindselect
+ decl['charselector'] = charselect
+ decl['typename'] = typename
+ for k in list(decl.keys()):
+ if not decl[k]:
+ del decl[k]
+ for r in markoutercomma(m1.group('after')).split('@,@'):
+ if '-' in r:
+ try:
+ begc, endc = [x.strip() for x in r.split('-')]
+ except Exception:
+ outmess(
+ 'analyzeline: expected "<char>-<char>" instead of "%s" in range list of implicit statement\n' % r)
+ continue
+ else:
+ begc = endc = r.strip()
+ if not len(begc) == len(endc) == 1:
+ outmess(
+ 'analyzeline: expected "<char>-<char>" instead of "%s" in range list of implicit statement (2)\n' % r)
+ continue
+ for o in range(ord(begc), ord(endc) + 1):
+ impl[chr(o)] = decl
+ groupcache[groupcounter]['implicit'] = impl
+ elif case == 'data':
+ ll = []
+ dl = ''
+ il = ''
+ f = 0
+ fc = 1
+ inp = 0
+ for c in m.group('after'):
+ if not inp:
+ if c == "'":
+ fc = not fc
+ if c == '/' and fc:
+ f = f + 1
+ continue
+ if c == '(':
+ inp = inp + 1
+ elif c == ')':
+ inp = inp - 1
+ if f == 0:
+ dl = dl + c
+ elif f == 1:
+ il = il + c
+ elif f == 2:
+ dl = dl.strip()
+ if dl.startswith(','):
+ dl = dl[1:].strip()
+ ll.append([dl, il])
+ dl = c
+ il = ''
+ f = 0
+ if f == 2:
+ dl = dl.strip()
+ if dl.startswith(','):
+ dl = dl[1:].strip()
+ ll.append([dl, il])
+ vars = groupcache[groupcounter].get('vars', {})
+ last_name = None
+ for l in ll:
+ l[0], l[1] = l[0].strip(), l[1].strip()
+ if l[0].startswith(','):
+ l[0] = l[0][1:]
+ if l[0].startswith('('):
+ outmess('analyzeline: implied-DO list "%s" is not supported. Skipping.\n' % l[0])
+ continue
+ for idx, v in enumerate(rmbadname([x.strip() for x in markoutercomma(l[0]).split('@,@')])):
+ if v.startswith('('):
+ outmess('analyzeline: implied-DO list "%s" is not supported. Skipping.\n' % v)
+ # XXX: subsequent init expressions may get wrong values.
+ # Ignoring since data statements are irrelevant for
+ # wrapping.
+ continue
+ if '!' in l[1]:
+ # Fixes gh-24746 pyf generation
+ # XXX: This essentially ignores the value for generating the pyf which is fine:
+ # integer dimension(3) :: mytab
+ # common /mycom/ mytab
+ # Since in any case it is initialized in the Fortran code
+ outmess('Comment line in declaration "%s" is not supported. Skipping.\n' % l[1])
+ continue
+ vars.setdefault(v, {})
+ vtype = vars[v].get('typespec')
+ vdim = getdimension(vars[v])
+ matches = re.findall(r"\(.*?\)", l[1]) if vtype == 'complex' else l[1].split(',')
+ try:
+ new_val = "(/{}/)".format(", ".join(matches)) if vdim else matches[idx]
+ except IndexError:
+ # gh-24746
+ # Runs only if above code fails. Fixes the line
+ # DATA IVAR1, IVAR2, IVAR3, IVAR4, EVAR5 /4*0,0.0D0/
+ # by expanding to ['0', '0', '0', '0', '0.0d0']
+ if any("*" in m for m in matches):
+ expanded_list = []
+ for match in matches:
+ if "*" in match:
+ try:
+ multiplier, value = match.split("*")
+ expanded_list.extend([value.strip()] * int(multiplier))
+ except ValueError: # if int(multiplier) fails
+ expanded_list.append(match.strip())
+ else:
+ expanded_list.append(match.strip())
+ matches = expanded_list
+ new_val = "(/{}/)".format(", ".join(matches)) if vdim else matches[idx]
+ current_val = vars[v].get('=')
+ if current_val and (current_val != new_val):
+ outmess('analyzeline: changing init expression of "%s" ("%s") to "%s"\n' % (v, current_val, new_val))
+ vars[v]['='] = new_val
+ last_name = v
+ groupcache[groupcounter]['vars'] = vars
+ if last_name:
+ previous_context = ('variable', last_name, groupcounter)
+ elif case == 'common':
+ line = m.group('after').strip()
+ if not line[0] == '/':
+ line = '//' + line
+ cl = []
+ f = 0
+ bn = ''
+ ol = ''
+ for c in line:
+ if c == '/':
+ f = f + 1
+ continue
+ if f >= 3:
+ bn = bn.strip()
+ if not bn:
+ bn = '_BLNK_'
+ cl.append([bn, ol])
+ f = f - 2
+ bn = ''
+ ol = ''
+ if f % 2:
+ bn = bn + c
+ else:
+ ol = ol + c
+ bn = bn.strip()
+ if not bn:
+ bn = '_BLNK_'
+ cl.append([bn, ol])
+ commonkey = {}
+ if 'common' in groupcache[groupcounter]:
+ commonkey = groupcache[groupcounter]['common']
+ for c in cl:
+ if c[0] not in commonkey:
+ commonkey[c[0]] = []
+ for i in [x.strip() for x in markoutercomma(c[1]).split('@,@')]:
+ if i:
+ commonkey[c[0]].append(i)
+ groupcache[groupcounter]['common'] = commonkey
+ previous_context = ('common', bn, groupcounter)
+ elif case == 'use':
+ m1 = re.match(
+ r'\A\s*(?P<name>\b\w+\b)\s*((,(\s*\bonly\b\s*:|(?P<notonly>))\s*(?P<list>.*))|)\s*\Z', m.group('after'), re.I)
+ if m1:
+ mm = m1.groupdict()
+ if 'use' not in groupcache[groupcounter]:
+ groupcache[groupcounter]['use'] = {}
+ name = m1.group('name')
+ groupcache[groupcounter]['use'][name] = {}
+ isonly = 0
+ if 'list' in mm and mm['list'] is not None:
+ if 'notonly' in mm and mm['notonly'] is None:
+ isonly = 1
+ groupcache[groupcounter]['use'][name]['only'] = isonly
+ ll = [x.strip() for x in mm['list'].split(',')]
+ rl = {}
+ for l in ll:
+ if '=' in l:
+ m2 = re.match(
+ r'\A\s*(?P<local>\b\w+\b)\s*=\s*>\s*(?P<use>\b\w+\b)\s*\Z', l, re.I)
+ if m2:
+ rl[m2.group('local').strip()] = m2.group(
+ 'use').strip()
+ else:
+ outmess(
+ 'analyzeline: Not local=>use pattern found in %s\n' % repr(l))
+ else:
+ rl[l] = l
+ groupcache[groupcounter]['use'][name]['map'] = rl
+ else:
+ pass
+ else:
+ print(m.groupdict())
+ outmess('analyzeline: Could not crack the use statement.\n')
+ elif case in ['f2pyenhancements']:
+ if 'f2pyenhancements' not in groupcache[groupcounter]:
+ groupcache[groupcounter]['f2pyenhancements'] = {}
+ d = groupcache[groupcounter]['f2pyenhancements']
+ if m.group('this') == 'usercode' and 'usercode' in d:
+ if isinstance(d['usercode'], str):
+ d['usercode'] = [d['usercode']]
+ d['usercode'].append(m.group('after'))
+ else:
+ d[m.group('this')] = m.group('after')
+ elif case == 'multiline':
+ if previous_context is None:
+ if verbose:
+ outmess('analyzeline: No context for multiline block.\n')
+ return
+ gc = groupcounter
+ appendmultiline(groupcache[gc],
+ previous_context[:2],
+ m.group('this'))
+ else:
+ if verbose > 1:
+ print(m.groupdict())
+ outmess('analyzeline: No code implemented for line.\n')
+
+
+def appendmultiline(group, context_name, ml):
+ if 'f2pymultilines' not in group:
+ group['f2pymultilines'] = {}
+ d = group['f2pymultilines']
+ if context_name not in d:
+ d[context_name] = []
+ d[context_name].append(ml)
+ return
+
+
+def cracktypespec0(typespec, ll):
+ selector = None
+ attr = None
+ if re.match(r'double\s*complex', typespec, re.I):
+ typespec = 'double complex'
+ elif re.match(r'double\s*precision', typespec, re.I):
+ typespec = 'double precision'
+ else:
+ typespec = typespec.strip().lower()
+ m1 = selectpattern.match(markouterparen(ll))
+ if not m1:
+ outmess(
+ 'cracktypespec0: no kind/char_selector pattern found for line.\n')
+ return
+ d = m1.groupdict()
+ for k in list(d.keys()):
+ d[k] = unmarkouterparen(d[k])
+ if typespec in ['complex', 'integer', 'logical', 'real', 'character', 'type']:
+ selector = d['this']
+ ll = d['after']
+ i = ll.find('::')
+ if i >= 0:
+ attr = ll[:i].strip()
+ ll = ll[i + 2:]
+ return typespec, selector, attr, ll
+#####
+namepattern = re.compile(r'\s*(?P<name>\b\w+\b)\s*(?P<after>.*)\s*\Z', re.I)
+kindselector = re.compile(
+ r'\s*(\(\s*(kind\s*=)?\s*(?P<kind>.*)\s*\)|\*\s*(?P<kind2>.*?))\s*\Z', re.I)
+charselector = re.compile(
+ r'\s*(\((?P<lenkind>.*)\)|\*\s*(?P<charlen>.*))\s*\Z', re.I)
+lenkindpattern = re.compile(
+ r'\s*(kind\s*=\s*(?P<kind>.*?)\s*(@,@\s*len\s*=\s*(?P<len>.*)|)'
+ r'|(len\s*=\s*|)(?P<len2>.*?)\s*(@,@\s*(kind\s*=\s*|)(?P<kind2>.*)'
+ r'|(f2py_len\s*=\s*(?P<f2py_len>.*))|))\s*\Z', re.I)
+lenarraypattern = re.compile(
+ r'\s*(@\(@\s*(?!/)\s*(?P<array>.*?)\s*@\)@\s*\*\s*(?P<len>.*?)|(\*\s*(?P<len2>.*?)|)\s*(@\(@\s*(?!/)\s*(?P<array2>.*?)\s*@\)@|))\s*(=\s*(?P<init>.*?)|(@\(@|)/\s*(?P<init2>.*?)\s*/(@\)@|)|)\s*\Z', re.I)
+
+
+def removespaces(expr):
+ expr = expr.strip()
+ if len(expr) <= 1:
+ return expr
+ expr2 = expr[0]
+ for i in range(1, len(expr) - 1):
+ if (expr[i] == ' ' and
+ ((expr[i + 1] in "()[]{}=+-/* ") or
+ (expr[i - 1] in "()[]{}=+-/* "))):
+ continue
+ expr2 = expr2 + expr[i]
+ expr2 = expr2 + expr[-1]
+ return expr2
+
+
+def markinnerspaces(line):
+ """
+ The function replace all spaces in the input variable line which are
+ surrounded with quotation marks, with the triplet "@_@".
+
+ For instance, for the input "a 'b c'" the function returns "a 'b@_@c'"
+
+ Parameters
+ ----------
+ line : str
+
+ Returns
+ -------
+ str
+
+ """
+ fragment = ''
+ inside = False
+ current_quote = None
+ escaped = ''
+ for c in line:
+ if escaped == '\\' and c in ['\\', '\'', '"']:
+ fragment += c
+ escaped = c
+ continue
+ if not inside and c in ['\'', '"']:
+ current_quote = c
+ if c == current_quote:
+ inside = not inside
+ elif c == ' ' and inside:
+ fragment += '@_@'
+ continue
+ fragment += c
+ escaped = c # reset to non-backslash
+ return fragment
+
+
+def updatevars(typespec, selector, attrspec, entitydecl):
+ """
+ Returns last_name, the variable name without special chars, parenthesis
+ or dimension specifiers.
+
+ Alters groupcache to add the name, typespec, attrspec (and possibly value)
+ of current variable.
+ """
+ global groupcache, groupcounter
+
+ last_name = None
+ kindselect, charselect, typename = cracktypespec(typespec, selector)
+ # Clean up outer commas, whitespace and undesired chars from attrspec
+ if attrspec:
+ attrspec = [x.strip() for x in markoutercomma(attrspec).split('@,@')]
+ l = []
+ c = re.compile(r'(?P<start>[a-zA-Z]+)')
+ for a in attrspec:
+ if not a:
+ continue
+ m = c.match(a)
+ if m:
+ s = m.group('start').lower()
+ a = s + a[len(s):]
+ l.append(a)
+ attrspec = l
+ el = [x.strip() for x in markoutercomma(entitydecl).split('@,@')]
+ el1 = []
+ for e in el:
+ for e1 in [x.strip() for x in markoutercomma(removespaces(markinnerspaces(e)), comma=' ').split('@ @')]:
+ if e1:
+ el1.append(e1.replace('@_@', ' '))
+ for e in el1:
+ m = namepattern.match(e)
+ if not m:
+ outmess(
+ 'updatevars: no name pattern found for entity=%s. Skipping.\n' % (repr(e)))
+ continue
+ ename = rmbadname1(m.group('name'))
+ edecl = {}
+ if ename in groupcache[groupcounter]['vars']:
+ edecl = groupcache[groupcounter]['vars'][ename].copy()
+ not_has_typespec = 'typespec' not in edecl
+ if not_has_typespec:
+ edecl['typespec'] = typespec
+ elif typespec and (not typespec == edecl['typespec']):
+ outmess('updatevars: attempt to change the type of "%s" ("%s") to "%s". Ignoring.\n' % (
+ ename, edecl['typespec'], typespec))
+ if 'kindselector' not in edecl:
+ edecl['kindselector'] = copy.copy(kindselect)
+ elif kindselect:
+ for k in list(kindselect.keys()):
+ if k in edecl['kindselector'] and (not kindselect[k] == edecl['kindselector'][k]):
+ outmess('updatevars: attempt to change the kindselector "%s" of "%s" ("%s") to "%s". Ignoring.\n' % (
+ k, ename, edecl['kindselector'][k], kindselect[k]))
+ else:
+ edecl['kindselector'][k] = copy.copy(kindselect[k])
+ if 'charselector' not in edecl and charselect:
+ if not_has_typespec:
+ edecl['charselector'] = charselect
+ else:
+ errmess('updatevars:%s: attempt to change empty charselector to %r. Ignoring.\n'
+ % (ename, charselect))
+ elif charselect:
+ for k in list(charselect.keys()):
+ if k in edecl['charselector'] and (not charselect[k] == edecl['charselector'][k]):
+ outmess('updatevars: attempt to change the charselector "%s" of "%s" ("%s") to "%s". Ignoring.\n' % (
+ k, ename, edecl['charselector'][k], charselect[k]))
+ else:
+ edecl['charselector'][k] = copy.copy(charselect[k])
+ if 'typename' not in edecl:
+ edecl['typename'] = typename
+ elif typename and (not edecl['typename'] == typename):
+ outmess('updatevars: attempt to change the typename of "%s" ("%s") to "%s". Ignoring.\n' % (
+ ename, edecl['typename'], typename))
+ if 'attrspec' not in edecl:
+ edecl['attrspec'] = copy.copy(attrspec)
+ elif attrspec:
+ for a in attrspec:
+ if a not in edecl['attrspec']:
+ edecl['attrspec'].append(a)
+ else:
+ edecl['typespec'] = copy.copy(typespec)
+ edecl['kindselector'] = copy.copy(kindselect)
+ edecl['charselector'] = copy.copy(charselect)
+ edecl['typename'] = typename
+ edecl['attrspec'] = copy.copy(attrspec)
+ if 'external' in (edecl.get('attrspec') or []) and e in groupcache[groupcounter]['args']:
+ if 'externals' not in groupcache[groupcounter]:
+ groupcache[groupcounter]['externals'] = []
+ groupcache[groupcounter]['externals'].append(e)
+ if m.group('after'):
+ m1 = lenarraypattern.match(markouterparen(m.group('after')))
+ if m1:
+ d1 = m1.groupdict()
+ for lk in ['len', 'array', 'init']:
+ if d1[lk + '2'] is not None:
+ d1[lk] = d1[lk + '2']
+ del d1[lk + '2']
+ for k in list(d1.keys()):
+ if d1[k] is not None:
+ d1[k] = unmarkouterparen(d1[k])
+ else:
+ del d1[k]
+
+ if 'len' in d1 and 'array' in d1:
+ if d1['len'] == '':
+ d1['len'] = d1['array']
+ del d1['array']
+ elif typespec == 'character':
+ if ('charselector' not in edecl) or (not edecl['charselector']):
+ edecl['charselector'] = {}
+ if 'len' in edecl['charselector']:
+ del edecl['charselector']['len']
+ edecl['charselector']['*'] = d1['len']
+ del d1['len']
+ else:
+ d1['array'] = d1['array'] + ',' + d1['len']
+ del d1['len']
+ errmess('updatevars: "%s %s" is mapped to "%s %s(%s)"\n' % (
+ typespec, e, typespec, ename, d1['array']))
+
+ if 'len' in d1:
+ if typespec in ['complex', 'integer', 'logical', 'real']:
+ if ('kindselector' not in edecl) or (not edecl['kindselector']):
+ edecl['kindselector'] = {}
+ edecl['kindselector']['*'] = d1['len']
+ del d1['len']
+ elif typespec == 'character':
+ if ('charselector' not in edecl) or (not edecl['charselector']):
+ edecl['charselector'] = {}
+ if 'len' in edecl['charselector']:
+ del edecl['charselector']['len']
+ edecl['charselector']['*'] = d1['len']
+ del d1['len']
+
+ if 'init' in d1:
+ if '=' in edecl and (not edecl['='] == d1['init']):
+ outmess('updatevars: attempt to change the init expression of "%s" ("%s") to "%s". Ignoring.\n' % (
+ ename, edecl['='], d1['init']))
+ else:
+ edecl['='] = d1['init']
+
+ if 'array' in d1:
+ dm = 'dimension(%s)' % d1['array']
+ if 'attrspec' not in edecl or (not edecl['attrspec']):
+ edecl['attrspec'] = [dm]
+ else:
+ edecl['attrspec'].append(dm)
+ for dm1 in edecl['attrspec']:
+ if dm1[:9] == 'dimension' and dm1 != dm:
+ del edecl['attrspec'][-1]
+ errmess('updatevars:%s: attempt to change %r to %r. Ignoring.\n'
+ % (ename, dm1, dm))
+ break
+
+ else:
+ outmess('updatevars: could not crack entity declaration "%s". Ignoring.\n' % (
+ ename + m.group('after')))
+ for k in list(edecl.keys()):
+ if not edecl[k]:
+ del edecl[k]
+ groupcache[groupcounter]['vars'][ename] = edecl
+ if 'varnames' in groupcache[groupcounter]:
+ groupcache[groupcounter]['varnames'].append(ename)
+ last_name = ename
+ return last_name
+
+
+def cracktypespec(typespec, selector):
+ kindselect = None
+ charselect = None
+ typename = None
+ if selector:
+ if typespec in ['complex', 'integer', 'logical', 'real']:
+ kindselect = kindselector.match(selector)
+ if not kindselect:
+ outmess(
+ 'cracktypespec: no kindselector pattern found for %s\n' % (repr(selector)))
+ return
+ kindselect = kindselect.groupdict()
+ kindselect['*'] = kindselect['kind2']
+ del kindselect['kind2']
+ for k in list(kindselect.keys()):
+ if not kindselect[k]:
+ del kindselect[k]
+ for k, i in list(kindselect.items()):
+ kindselect[k] = rmbadname1(i)
+ elif typespec == 'character':
+ charselect = charselector.match(selector)
+ if not charselect:
+ outmess(
+ 'cracktypespec: no charselector pattern found for %s\n' % (repr(selector)))
+ return
+ charselect = charselect.groupdict()
+ charselect['*'] = charselect['charlen']
+ del charselect['charlen']
+ if charselect['lenkind']:
+ lenkind = lenkindpattern.match(
+ markoutercomma(charselect['lenkind']))
+ lenkind = lenkind.groupdict()
+ for lk in ['len', 'kind']:
+ if lenkind[lk + '2']:
+ lenkind[lk] = lenkind[lk + '2']
+ charselect[lk] = lenkind[lk]
+ del lenkind[lk + '2']
+ if lenkind['f2py_len'] is not None:
+ # used to specify the length of assumed length strings
+ charselect['f2py_len'] = lenkind['f2py_len']
+ del charselect['lenkind']
+ for k in list(charselect.keys()):
+ if not charselect[k]:
+ del charselect[k]
+ for k, i in list(charselect.items()):
+ charselect[k] = rmbadname1(i)
+ elif typespec == 'type':
+ typename = re.match(r'\s*\(\s*(?P<name>\w+)\s*\)', selector, re.I)
+ if typename:
+ typename = typename.group('name')
+ else:
+ outmess('cracktypespec: no typename found in %s\n' %
+ (repr(typespec + selector)))
+ else:
+ outmess('cracktypespec: no selector used for %s\n' %
+ (repr(selector)))
+ return kindselect, charselect, typename
+######
+
+
+def setattrspec(decl, attr, force=0):
+ if not decl:
+ decl = {}
+ if not attr:
+ return decl
+ if 'attrspec' not in decl:
+ decl['attrspec'] = [attr]
+ return decl
+ if force:
+ decl['attrspec'].append(attr)
+ if attr in decl['attrspec']:
+ return decl
+ if attr == 'static' and 'automatic' not in decl['attrspec']:
+ decl['attrspec'].append(attr)
+ elif attr == 'automatic' and 'static' not in decl['attrspec']:
+ decl['attrspec'].append(attr)
+ elif attr == 'public':
+ if 'private' not in decl['attrspec']:
+ decl['attrspec'].append(attr)
+ elif attr == 'private':
+ if 'public' not in decl['attrspec']:
+ decl['attrspec'].append(attr)
+ else:
+ decl['attrspec'].append(attr)
+ return decl
+
+
+def setkindselector(decl, sel, force=0):
+ if not decl:
+ decl = {}
+ if not sel:
+ return decl
+ if 'kindselector' not in decl:
+ decl['kindselector'] = sel
+ return decl
+ for k in list(sel.keys()):
+ if force or k not in decl['kindselector']:
+ decl['kindselector'][k] = sel[k]
+ return decl
+
+
+def setcharselector(decl, sel, force=0):
+ if not decl:
+ decl = {}
+ if not sel:
+ return decl
+ if 'charselector' not in decl:
+ decl['charselector'] = sel
+ return decl
+
+ for k in list(sel.keys()):
+ if force or k not in decl['charselector']:
+ decl['charselector'][k] = sel[k]
+ return decl
+
+
+def getblockname(block, unknown='unknown'):
+ if 'name' in block:
+ return block['name']
+ return unknown
+
+# post processing
+
+
+def setmesstext(block):
+ global filepositiontext
+
+ try:
+ filepositiontext = 'In: %s:%s\n' % (block['from'], block['name'])
+ except Exception:
+ pass
+
+
+def get_usedict(block):
+ usedict = {}
+ if 'parent_block' in block:
+ usedict = get_usedict(block['parent_block'])
+ if 'use' in block:
+ usedict.update(block['use'])
+ return usedict
+
+
+def get_useparameters(block, param_map=None):
+ global f90modulevars
+
+ if param_map is None:
+ param_map = {}
+ usedict = get_usedict(block)
+ if not usedict:
+ return param_map
+ for usename, mapping in list(usedict.items()):
+ usename = usename.lower()
+ if usename not in f90modulevars:
+ outmess('get_useparameters: no module %s info used by %s\n' %
+ (usename, block.get('name')))
+ continue
+ mvars = f90modulevars[usename]
+ params = get_parameters(mvars)
+ if not params:
+ continue
+ # XXX: apply mapping
+ if mapping:
+ errmess('get_useparameters: mapping for %s not impl.\n' % (mapping))
+ for k, v in list(params.items()):
+ if k in param_map:
+ outmess('get_useparameters: overriding parameter %s with'
+ ' value from module %s\n' % (repr(k), repr(usename)))
+ param_map[k] = v
+
+ return param_map
+
+
+def postcrack2(block, tab='', param_map=None):
+ global f90modulevars
+
+ if not f90modulevars:
+ return block
+ if isinstance(block, list):
+ ret = [postcrack2(g, tab=tab + '\t', param_map=param_map)
+ for g in block]
+ return ret
+ setmesstext(block)
+ outmess('%sBlock: %s\n' % (tab, block['name']), 0)
+
+ if param_map is None:
+ param_map = get_useparameters(block)
+
+ if param_map is not None and 'vars' in block:
+ vars = block['vars']
+ for n in list(vars.keys()):
+ var = vars[n]
+ if 'kindselector' in var:
+ kind = var['kindselector']
+ if 'kind' in kind:
+ val = kind['kind']
+ if val in param_map:
+ kind['kind'] = param_map[val]
+ new_body = [postcrack2(b, tab=tab + '\t', param_map=param_map)
+ for b in block['body']]
+ block['body'] = new_body
+
+ return block
+
+
+def postcrack(block, args=None, tab=''):
+ """
+ TODO:
+ function return values
+ determine expression types if in argument list
+ """
+ global usermodules, onlyfunctions
+
+ if isinstance(block, list):
+ gret = []
+ uret = []
+ for g in block:
+ setmesstext(g)
+ g = postcrack(g, tab=tab + '\t')
+ # sort user routines to appear first
+ if 'name' in g and '__user__' in g['name']:
+ uret.append(g)
+ else:
+ gret.append(g)
+ return uret + gret
+ setmesstext(block)
+ if not isinstance(block, dict) and 'block' not in block:
+ raise Exception('postcrack: Expected block dictionary instead of ' +
+ str(block))
+ if 'name' in block and not block['name'] == 'unknown_interface':
+ outmess('%sBlock: %s\n' % (tab, block['name']), 0)
+ block = analyzeargs(block)
+ block = analyzecommon(block)
+ block['vars'] = analyzevars(block)
+ block['sortvars'] = sortvarnames(block['vars'])
+ if 'args' in block and block['args']:
+ args = block['args']
+ block['body'] = analyzebody(block, args, tab=tab)
+
+ userisdefined = []
+ if 'use' in block:
+ useblock = block['use']
+ for k in list(useblock.keys()):
+ if '__user__' in k:
+ userisdefined.append(k)
+ else:
+ useblock = {}
+ name = ''
+ if 'name' in block:
+ name = block['name']
+ # and not userisdefined: # Build a __user__ module
+ if 'externals' in block and block['externals']:
+ interfaced = []
+ if 'interfaced' in block:
+ interfaced = block['interfaced']
+ mvars = copy.copy(block['vars'])
+ if name:
+ mname = name + '__user__routines'
+ else:
+ mname = 'unknown__user__routines'
+ if mname in userisdefined:
+ i = 1
+ while '%s_%i' % (mname, i) in userisdefined:
+ i = i + 1
+ mname = '%s_%i' % (mname, i)
+ interface = {'block': 'interface', 'body': [],
+ 'vars': {}, 'name': name + '_user_interface'}
+ for e in block['externals']:
+ if e in interfaced:
+ edef = []
+ j = -1
+ for b in block['body']:
+ j = j + 1
+ if b['block'] == 'interface':
+ i = -1
+ for bb in b['body']:
+ i = i + 1
+ if 'name' in bb and bb['name'] == e:
+ edef = copy.copy(bb)
+ del b['body'][i]
+ break
+ if edef:
+ if not b['body']:
+ del block['body'][j]
+ del interfaced[interfaced.index(e)]
+ break
+ interface['body'].append(edef)
+ else:
+ if e in mvars and not isexternal(mvars[e]):
+ interface['vars'][e] = mvars[e]
+ if interface['vars'] or interface['body']:
+ block['interfaced'] = interfaced
+ mblock = {'block': 'python module', 'body': [
+ interface], 'vars': {}, 'name': mname, 'interfaced': block['externals']}
+ useblock[mname] = {}
+ usermodules.append(mblock)
+ if useblock:
+ block['use'] = useblock
+ return block
+
+
+def sortvarnames(vars):
+ indep = []
+ dep = []
+ for v in list(vars.keys()):
+ if 'depend' in vars[v] and vars[v]['depend']:
+ dep.append(v)
+ else:
+ indep.append(v)
+ n = len(dep)
+ i = 0
+ while dep: # XXX: How to catch dependence cycles correctly?
+ v = dep[0]
+ fl = 0
+ for w in dep[1:]:
+ if w in vars[v]['depend']:
+ fl = 1
+ break
+ if fl:
+ dep = dep[1:] + [v]
+ i = i + 1
+ if i > n:
+ errmess('sortvarnames: failed to compute dependencies because'
+ ' of cyclic dependencies between '
+ + ', '.join(dep) + '\n')
+ indep = indep + dep
+ break
+ else:
+ indep.append(v)
+ dep = dep[1:]
+ n = len(dep)
+ i = 0
+ return indep
+
+
+def analyzecommon(block):
+ if not hascommon(block):
+ return block
+ commonvars = []
+ for k in list(block['common'].keys()):
+ comvars = []
+ for e in block['common'][k]:
+ m = re.match(
+ r'\A\s*\b(?P<name>.*?)\b\s*(\((?P<dims>.*?)\)|)\s*\Z', e, re.I)
+ if m:
+ dims = []
+ if m.group('dims'):
+ dims = [x.strip()
+ for x in markoutercomma(m.group('dims')).split('@,@')]
+ n = rmbadname1(m.group('name').strip())
+ if n in block['vars']:
+ if 'attrspec' in block['vars'][n]:
+ block['vars'][n]['attrspec'].append(
+ 'dimension(%s)' % (','.join(dims)))
+ else:
+ block['vars'][n]['attrspec'] = [
+ 'dimension(%s)' % (','.join(dims))]
+ else:
+ if dims:
+ block['vars'][n] = {
+ 'attrspec': ['dimension(%s)' % (','.join(dims))]}
+ else:
+ block['vars'][n] = {}
+ if n not in commonvars:
+ commonvars.append(n)
+ else:
+ n = e
+ errmess(
+ 'analyzecommon: failed to extract "<name>[(<dims>)]" from "%s" in common /%s/.\n' % (e, k))
+ comvars.append(n)
+ block['common'][k] = comvars
+ if 'commonvars' not in block:
+ block['commonvars'] = commonvars
+ else:
+ block['commonvars'] = block['commonvars'] + commonvars
+ return block
+
+
+def analyzebody(block, args, tab=''):
+ global usermodules, skipfuncs, onlyfuncs, f90modulevars
+
+ setmesstext(block)
+
+ maybe_private = {
+ key: value
+ for key, value in block['vars'].items()
+ if 'attrspec' not in value or 'public' not in value['attrspec']
+ }
+
+ body = []
+ for b in block['body']:
+ b['parent_block'] = block
+ if b['block'] in ['function', 'subroutine']:
+ if args is not None and b['name'] not in args:
+ continue
+ else:
+ as_ = b['args']
+ # Add private members to skipfuncs for gh-23879
+ if b['name'] in maybe_private.keys():
+ skipfuncs.append(b['name'])
+ if b['name'] in skipfuncs:
+ continue
+ if onlyfuncs and b['name'] not in onlyfuncs:
+ continue
+ b['saved_interface'] = crack2fortrangen(
+ b, '\n' + ' ' * 6, as_interface=True)
+
+ else:
+ as_ = args
+ b = postcrack(b, as_, tab=tab + '\t')
+ if b['block'] in ['interface', 'abstract interface'] and \
+ not b['body'] and not b.get('implementedby'):
+ if 'f2pyenhancements' not in b:
+ continue
+ if b['block'].replace(' ', '') == 'pythonmodule':
+ usermodules.append(b)
+ else:
+ if b['block'] == 'module':
+ f90modulevars[b['name']] = b['vars']
+ body.append(b)
+ return body
+
+
+def buildimplicitrules(block):
+ setmesstext(block)
+ implicitrules = defaultimplicitrules
+ attrrules = {}
+ if 'implicit' in block:
+ if block['implicit'] is None:
+ implicitrules = None
+ if verbose > 1:
+ outmess(
+ 'buildimplicitrules: no implicit rules for routine %s.\n' % repr(block['name']))
+ else:
+ for k in list(block['implicit'].keys()):
+ if block['implicit'][k].get('typespec') not in ['static', 'automatic']:
+ implicitrules[k] = block['implicit'][k]
+ else:
+ attrrules[k] = block['implicit'][k]['typespec']
+ return implicitrules, attrrules
+
+
+def myeval(e, g=None, l=None):
+ """ Like `eval` but returns only integers and floats """
+ r = eval(e, g, l)
+ if type(r) in [int, float]:
+ return r
+ raise ValueError('r=%r' % (r))
+
+getlincoef_re_1 = re.compile(r'\A\b\w+\b\Z', re.I)
+
+
+def getlincoef(e, xset): # e = a*x+b ; x in xset
+ """
+ Obtain ``a`` and ``b`` when ``e == "a*x+b"``, where ``x`` is a symbol in
+ xset.
+
+ >>> getlincoef('2*x + 1', {'x'})
+ (2, 1, 'x')
+ >>> getlincoef('3*x + x*2 + 2 + 1', {'x'})
+ (5, 3, 'x')
+ >>> getlincoef('0', {'x'})
+ (0, 0, None)
+ >>> getlincoef('0*x', {'x'})
+ (0, 0, 'x')
+ >>> getlincoef('x*x', {'x'})
+ (None, None, None)
+
+ This can be tricked by sufficiently complex expressions
+
+ >>> getlincoef('(x - 0.5)*(x - 1.5)*(x - 1)*x + 2*x + 3', {'x'})
+ (2.0, 3.0, 'x')
+ """
+ try:
+ c = int(myeval(e, {}, {}))
+ return 0, c, None
+ except Exception:
+ pass
+ if getlincoef_re_1.match(e):
+ return 1, 0, e
+ len_e = len(e)
+ for x in xset:
+ if len(x) > len_e:
+ continue
+ if re.search(r'\w\s*\([^)]*\b' + x + r'\b', e):
+ # skip function calls having x as an argument, e.g max(1, x)
+ continue
+ re_1 = re.compile(r'(?P<before>.*?)\b' + x + r'\b(?P<after>.*)', re.I)
+ m = re_1.match(e)
+ if m:
+ try:
+ m1 = re_1.match(e)
+ while m1:
+ ee = '%s(%s)%s' % (
+ m1.group('before'), 0, m1.group('after'))
+ m1 = re_1.match(ee)
+ b = myeval(ee, {}, {})
+ m1 = re_1.match(e)
+ while m1:
+ ee = '%s(%s)%s' % (
+ m1.group('before'), 1, m1.group('after'))
+ m1 = re_1.match(ee)
+ a = myeval(ee, {}, {}) - b
+ m1 = re_1.match(e)
+ while m1:
+ ee = '%s(%s)%s' % (
+ m1.group('before'), 0.5, m1.group('after'))
+ m1 = re_1.match(ee)
+ c = myeval(ee, {}, {})
+ # computing another point to be sure that expression is linear
+ m1 = re_1.match(e)
+ while m1:
+ ee = '%s(%s)%s' % (
+ m1.group('before'), 1.5, m1.group('after'))
+ m1 = re_1.match(ee)
+ c2 = myeval(ee, {}, {})
+ if (a * 0.5 + b == c and a * 1.5 + b == c2):
+ return a, b, x
+ except Exception:
+ pass
+ break
+ return None, None, None
+
+
+word_pattern = re.compile(r'\b[a-z][\w$]*\b', re.I)
+
+
+def _get_depend_dict(name, vars, deps):
+ if name in vars:
+ words = vars[name].get('depend', [])
+
+ if '=' in vars[name] and not isstring(vars[name]):
+ for word in word_pattern.findall(vars[name]['=']):
+ # The word_pattern may return values that are not
+ # only variables, they can be string content for instance
+ if word not in words and word in vars and word != name:
+ words.append(word)
+ for word in words[:]:
+ for w in deps.get(word, []) \
+ or _get_depend_dict(word, vars, deps):
+ if w not in words:
+ words.append(w)
+ else:
+ outmess('_get_depend_dict: no dependence info for %s\n' % (repr(name)))
+ words = []
+ deps[name] = words
+ return words
+
+
+def _calc_depend_dict(vars):
+ names = list(vars.keys())
+ depend_dict = {}
+ for n in names:
+ _get_depend_dict(n, vars, depend_dict)
+ return depend_dict
+
+
+def get_sorted_names(vars):
+ depend_dict = _calc_depend_dict(vars)
+ names = []
+ for name in list(depend_dict.keys()):
+ if not depend_dict[name]:
+ names.append(name)
+ del depend_dict[name]
+ while depend_dict:
+ for name, lst in list(depend_dict.items()):
+ new_lst = [n for n in lst if n in depend_dict]
+ if not new_lst:
+ names.append(name)
+ del depend_dict[name]
+ else:
+ depend_dict[name] = new_lst
+ return [name for name in names if name in vars]
+
+
+def _kind_func(string):
+ # XXX: return something sensible.
+ if string[0] in "'\"":
+ string = string[1:-1]
+ if real16pattern.match(string):
+ return 8
+ elif real8pattern.match(string):
+ return 4
+ return 'kind(' + string + ')'
+
+
+def _selected_int_kind_func(r):
+ # XXX: This should be processor dependent
+ m = 10 ** r
+ if m <= 2 ** 8:
+ return 1
+ if m <= 2 ** 16:
+ return 2
+ if m <= 2 ** 32:
+ return 4
+ if m <= 2 ** 63:
+ return 8
+ if m <= 2 ** 128:
+ return 16
+ return -1
+
+
+def _selected_real_kind_func(p, r=0, radix=0):
+ # XXX: This should be processor dependent
+ # This is only verified for 0 <= p <= 20, possibly good for p <= 33 and above
+ if p < 7:
+ return 4
+ if p < 16:
+ return 8
+ machine = platform.machine().lower()
+ if machine.startswith(('aarch64', 'alpha', 'arm64', 'loongarch', 'mips', 'power', 'ppc', 'riscv', 's390x', 'sparc')):
+ if p <= 33:
+ return 16
+ else:
+ if p < 19:
+ return 10
+ elif p <= 33:
+ return 16
+ return -1
+
+
+def get_parameters(vars, global_params={}):
+ params = copy.copy(global_params)
+ g_params = copy.copy(global_params)
+ for name, func in [('kind', _kind_func),
+ ('selected_int_kind', _selected_int_kind_func),
+ ('selected_real_kind', _selected_real_kind_func), ]:
+ if name not in g_params:
+ g_params[name] = func
+ param_names = []
+ for n in get_sorted_names(vars):
+ if 'attrspec' in vars[n] and 'parameter' in vars[n]['attrspec']:
+ param_names.append(n)
+ kind_re = re.compile(r'\bkind\s*\(\s*(?P<value>.*)\s*\)', re.I)
+ selected_int_kind_re = re.compile(
+ r'\bselected_int_kind\s*\(\s*(?P<value>.*)\s*\)', re.I)
+ selected_kind_re = re.compile(
+ r'\bselected_(int|real)_kind\s*\(\s*(?P<value>.*)\s*\)', re.I)
+ for n in param_names:
+ if '=' in vars[n]:
+ v = vars[n]['=']
+ if islogical(vars[n]):
+ v = v.lower()
+ for repl in [
+ ('.false.', 'False'),
+ ('.true.', 'True'),
+ # TODO: test .eq., .neq., etc replacements.
+ ]:
+ v = v.replace(*repl)
+
+ v = kind_re.sub(r'kind("\1")', v)
+ v = selected_int_kind_re.sub(r'selected_int_kind(\1)', v)
+
+ # We need to act according to the data.
+ # The easy case is if the data has a kind-specifier,
+ # then we may easily remove those specifiers.
+ # However, it may be that the user uses other specifiers...(!)
+ is_replaced = False
+
+ if 'kindselector' in vars[n]:
+ # Remove kind specifier (including those defined
+ # by parameters)
+ if 'kind' in vars[n]['kindselector']:
+ orig_v_len = len(v)
+ v = v.replace('_' + vars[n]['kindselector']['kind'], '')
+ # Again, this will be true if even a single specifier
+ # has been replaced, see comment above.
+ is_replaced = len(v) < orig_v_len
+
+ if not is_replaced:
+ if not selected_kind_re.match(v):
+ v_ = v.split('_')
+ # In case there are additive parameters
+ if len(v_) > 1:
+ v = ''.join(v_[:-1]).lower().replace(v_[-1].lower(), '')
+
+ # Currently this will not work for complex numbers.
+ # There is missing code for extracting a complex number,
+ # which may be defined in either of these:
+ # a) (Re, Im)
+ # b) cmplx(Re, Im)
+ # c) dcmplx(Re, Im)
+ # d) cmplx(Re, Im, <prec>)
+
+ if isdouble(vars[n]):
+ tt = list(v)
+ for m in real16pattern.finditer(v):
+ tt[m.start():m.end()] = list(
+ v[m.start():m.end()].lower().replace('d', 'e'))
+ v = ''.join(tt)
+
+ elif iscomplex(vars[n]):
+ outmess(f'get_parameters[TODO]: '
+ f'implement evaluation of complex expression {v}\n')
+
+ dimspec = ([s.lstrip('dimension').strip()
+ for s in vars[n]['attrspec']
+ if s.startswith('dimension')] or [None])[0]
+
+ # Handle _dp for gh-6624
+ # Also fixes gh-20460
+ if real16pattern.search(v):
+ v = 8
+ elif real8pattern.search(v):
+ v = 4
+ try:
+ params[n] = param_eval(v, g_params, params, dimspec=dimspec)
+ except Exception as msg:
+ params[n] = v
+ outmess(f'get_parameters: got "{msg}" on {n!r}\n')
+
+ if isstring(vars[n]) and isinstance(params[n], int):
+ params[n] = chr(params[n])
+ nl = n.lower()
+ if nl != n:
+ params[nl] = params[n]
+ else:
+ print(vars[n])
+ outmess(f'get_parameters:parameter {n!r} does not have value?!\n')
+ return params
+
+
+def _eval_length(length, params):
+ if length in ['(:)', '(*)', '*']:
+ return '(*)'
+ return _eval_scalar(length, params)
+
+
+_is_kind_number = re.compile(r'\d+_').match
+
+
+def _eval_scalar(value, params):
+ if _is_kind_number(value):
+ value = value.split('_')[0]
+ try:
+ # TODO: use symbolic from PR #19805
+ value = eval(value, {}, params)
+ value = (repr if isinstance(value, str) else str)(value)
+ except (NameError, SyntaxError, TypeError):
+ return value
+ except Exception as msg:
+ errmess('"%s" in evaluating %r '
+ '(available names: %s)\n'
+ % (msg, value, list(params.keys())))
+ return value
+
+
+def analyzevars(block):
+ """
+ Sets correct dimension information for each variable/parameter
+ """
+
+ global f90modulevars
+
+ setmesstext(block)
+ implicitrules, attrrules = buildimplicitrules(block)
+ vars = copy.copy(block['vars'])
+ if block['block'] == 'function' and block['name'] not in vars:
+ vars[block['name']] = {}
+ if '' in block['vars']:
+ del vars['']
+ if 'attrspec' in block['vars']['']:
+ gen = block['vars']['']['attrspec']
+ for n in set(vars) | set(b['name'] for b in block['body']):
+ for k in ['public', 'private']:
+ if k in gen:
+ vars[n] = setattrspec(vars.get(n, {}), k)
+ svars = []
+ args = block['args']
+ for a in args:
+ try:
+ vars[a]
+ svars.append(a)
+ except KeyError:
+ pass
+ for n in list(vars.keys()):
+ if n not in args:
+ svars.append(n)
+
+ params = get_parameters(vars, get_useparameters(block))
+ # At this point, params are read and interpreted, but
+ # the params used to define vars are not yet parsed
+ dep_matches = {}
+ name_match = re.compile(r'[A-Za-z][\w$]*').match
+ for v in list(vars.keys()):
+ m = name_match(v)
+ if m:
+ n = v[m.start():m.end()]
+ try:
+ dep_matches[n]
+ except KeyError:
+ dep_matches[n] = re.compile(r'.*\b%s\b' % (v), re.I).match
+ for n in svars:
+ if n[0] in list(attrrules.keys()):
+ vars[n] = setattrspec(vars[n], attrrules[n[0]])
+ if 'typespec' not in vars[n]:
+ if not('attrspec' in vars[n] and 'external' in vars[n]['attrspec']):
+ if implicitrules:
+ ln0 = n[0].lower()
+ for k in list(implicitrules[ln0].keys()):
+ if k == 'typespec' and implicitrules[ln0][k] == 'undefined':
+ continue
+ if k not in vars[n]:
+ vars[n][k] = implicitrules[ln0][k]
+ elif k == 'attrspec':
+ for l in implicitrules[ln0][k]:
+ vars[n] = setattrspec(vars[n], l)
+ elif n in block['args']:
+ outmess('analyzevars: typespec of variable %s is not defined in routine %s.\n' % (
+ repr(n), block['name']))
+ if 'charselector' in vars[n]:
+ if 'len' in vars[n]['charselector']:
+ l = vars[n]['charselector']['len']
+ try:
+ l = str(eval(l, {}, params))
+ except Exception:
+ pass
+ vars[n]['charselector']['len'] = l
+
+ if 'kindselector' in vars[n]:
+ if 'kind' in vars[n]['kindselector']:
+ l = vars[n]['kindselector']['kind']
+ try:
+ l = str(eval(l, {}, params))
+ except Exception:
+ pass
+ vars[n]['kindselector']['kind'] = l
+
+ dimension_exprs = {}
+ if 'attrspec' in vars[n]:
+ attr = vars[n]['attrspec']
+ attr.reverse()
+ vars[n]['attrspec'] = []
+ dim, intent, depend, check, note = None, None, None, None, None
+ for a in attr:
+ if a[:9] == 'dimension':
+ dim = (a[9:].strip())[1:-1]
+ elif a[:6] == 'intent':
+ intent = (a[6:].strip())[1:-1]
+ elif a[:6] == 'depend':
+ depend = (a[6:].strip())[1:-1]
+ elif a[:5] == 'check':
+ check = (a[5:].strip())[1:-1]
+ elif a[:4] == 'note':
+ note = (a[4:].strip())[1:-1]
+ else:
+ vars[n] = setattrspec(vars[n], a)
+ if intent:
+ if 'intent' not in vars[n]:
+ vars[n]['intent'] = []
+ for c in [x.strip() for x in markoutercomma(intent).split('@,@')]:
+ # Remove spaces so that 'in out' becomes 'inout'
+ tmp = c.replace(' ', '')
+ if tmp not in vars[n]['intent']:
+ vars[n]['intent'].append(tmp)
+ intent = None
+ if note:
+ note = note.replace('\\n\\n', '\n\n')
+ note = note.replace('\\n ', '\n')
+ if 'note' not in vars[n]:
+ vars[n]['note'] = [note]
+ else:
+ vars[n]['note'].append(note)
+ note = None
+ if depend is not None:
+ if 'depend' not in vars[n]:
+ vars[n]['depend'] = []
+ for c in rmbadname([x.strip() for x in markoutercomma(depend).split('@,@')]):
+ if c not in vars[n]['depend']:
+ vars[n]['depend'].append(c)
+ depend = None
+ if check is not None:
+ if 'check' not in vars[n]:
+ vars[n]['check'] = []
+ for c in [x.strip() for x in markoutercomma(check).split('@,@')]:
+ if c not in vars[n]['check']:
+ vars[n]['check'].append(c)
+ check = None
+ if dim and 'dimension' not in vars[n]:
+ vars[n]['dimension'] = []
+ for d in rmbadname(
+ [x.strip() for x in markoutercomma(dim).split('@,@')]
+ ):
+ # d is the expression inside the dimension declaration
+ # Evaluate `d` with respect to params
+ try:
+ # the dimension for this variable depends on a
+ # previously defined parameter
+ d = param_parse(d, params)
+ except (ValueError, IndexError, KeyError):
+ outmess(
+ ('analyzevars: could not parse dimension for '
+ f'variable {d!r}\n')
+ )
+
+ dim_char = ':' if d == ':' else '*'
+ if d == dim_char:
+ dl = [dim_char]
+ else:
+ dl = markoutercomma(d, ':').split('@:@')
+ if len(dl) == 2 and '*' in dl: # e.g. dimension(5:*)
+ dl = ['*']
+ d = '*'
+ if len(dl) == 1 and dl[0] != dim_char:
+ dl = ['1', dl[0]]
+ if len(dl) == 2:
+ d1, d2 = map(symbolic.Expr.parse, dl)
+ dsize = d2 - d1 + 1
+ d = dsize.tostring(language=symbolic.Language.C)
+ # find variables v that define d as a linear
+ # function, `d == a * v + b`, and store
+ # coefficients a and b for further analysis.
+ solver_and_deps = {}
+ for v in block['vars']:
+ s = symbolic.as_symbol(v)
+ if dsize.contains(s):
+ try:
+ a, b = dsize.linear_solve(s)
+
+ def solve_v(s, a=a, b=b):
+ return (s - b) / a
+
+ all_symbols = set(a.symbols())
+ all_symbols.update(b.symbols())
+ except RuntimeError as msg:
+ # d is not a linear function of v,
+ # however, if v can be determined
+ # from d using other means,
+ # implement the corresponding
+ # solve_v function here.
+ solve_v = None
+ all_symbols = set(dsize.symbols())
+ v_deps = set(
+ s.data for s in all_symbols
+ if s.data in vars)
+ solver_and_deps[v] = solve_v, list(v_deps)
+ # Note that dsize may contain symbols that are
+ # not defined in block['vars']. Here we assume
+ # these correspond to Fortran/C intrinsic
+ # functions or that are defined by other
+ # means. We'll let the compiler validate the
+ # definiteness of such symbols.
+ dimension_exprs[d] = solver_and_deps
+ vars[n]['dimension'].append(d)
+
+ if 'check' not in vars[n] and 'args' in block and n in block['args']:
+ # n is an argument that has no checks defined. Here we
+ # generate some consistency checks for n, and when n is an
+ # array, generate checks for its dimensions and construct
+ # initialization expressions.
+ n_deps = vars[n].get('depend', [])
+ n_checks = []
+ n_is_input = l_or(isintent_in, isintent_inout,
+ isintent_inplace)(vars[n])
+ if isarray(vars[n]): # n is array
+ for i, d in enumerate(vars[n]['dimension']):
+ coeffs_and_deps = dimension_exprs.get(d)
+ if coeffs_and_deps is None:
+ # d is `:` or `*` or a constant expression
+ pass
+ elif n_is_input:
+ # n is an input array argument and its shape
+ # may define variables used in dimension
+ # specifications.
+ for v, (solver, deps) in coeffs_and_deps.items():
+ def compute_deps(v, deps):
+ for v1 in coeffs_and_deps.get(v, [None, []])[1]:
+ if v1 not in deps:
+ deps.add(v1)
+ compute_deps(v1, deps)
+ all_deps = set()
+ compute_deps(v, all_deps)
+ if ((v in n_deps
+ or '=' in vars[v]
+ or 'depend' in vars[v])):
+ # Skip a variable that
+ # - n depends on
+ # - has user-defined initialization expression
+ # - has user-defined dependencies
+ continue
+ if solver is not None and v not in all_deps:
+ # v can be solved from d, hence, we
+ # make it an optional argument with
+ # initialization expression:
+ is_required = False
+ init = solver(symbolic.as_symbol(
+ f'shape({n}, {i})'))
+ init = init.tostring(
+ language=symbolic.Language.C)
+ vars[v]['='] = init
+ # n needs to be initialized before v. So,
+ # making v dependent on n and on any
+ # variables in solver or d.
+ vars[v]['depend'] = [n] + deps
+ if 'check' not in vars[v]:
+ # add check only when no
+ # user-specified checks exist
+ vars[v]['check'] = [
+ f'shape({n}, {i}) == {d}']
+ else:
+ # d is a non-linear function on v,
+ # hence, v must be a required input
+ # argument that n will depend on
+ is_required = True
+ if 'intent' not in vars[v]:
+ vars[v]['intent'] = []
+ if 'in' not in vars[v]['intent']:
+ vars[v]['intent'].append('in')
+ # v needs to be initialized before n
+ n_deps.append(v)
+ n_checks.append(
+ f'shape({n}, {i}) == {d}')
+ v_attr = vars[v].get('attrspec', [])
+ if not ('optional' in v_attr
+ or 'required' in v_attr):
+ v_attr.append(
+ 'required' if is_required else 'optional')
+ if v_attr:
+ vars[v]['attrspec'] = v_attr
+ if coeffs_and_deps is not None:
+ # extend v dependencies with ones specified in attrspec
+ for v, (solver, deps) in coeffs_and_deps.items():
+ v_deps = vars[v].get('depend', [])
+ for aa in vars[v].get('attrspec', []):
+ if aa.startswith('depend'):
+ aa = ''.join(aa.split())
+ v_deps.extend(aa[7:-1].split(','))
+ if v_deps:
+ vars[v]['depend'] = list(set(v_deps))
+ if n not in v_deps:
+ n_deps.append(v)
+ elif isstring(vars[n]):
+ if 'charselector' in vars[n]:
+ if '*' in vars[n]['charselector']:
+ length = _eval_length(vars[n]['charselector']['*'],
+ params)
+ vars[n]['charselector']['*'] = length
+ elif 'len' in vars[n]['charselector']:
+ length = _eval_length(vars[n]['charselector']['len'],
+ params)
+ del vars[n]['charselector']['len']
+ vars[n]['charselector']['*'] = length
+ if n_checks:
+ vars[n]['check'] = n_checks
+ if n_deps:
+ vars[n]['depend'] = list(set(n_deps))
+
+ if '=' in vars[n]:
+ if 'attrspec' not in vars[n]:
+ vars[n]['attrspec'] = []
+ if ('optional' not in vars[n]['attrspec']) and \
+ ('required' not in vars[n]['attrspec']):
+ vars[n]['attrspec'].append('optional')
+ if 'depend' not in vars[n]:
+ vars[n]['depend'] = []
+ for v, m in list(dep_matches.items()):
+ if m(vars[n]['=']):
+ vars[n]['depend'].append(v)
+ if not vars[n]['depend']:
+ del vars[n]['depend']
+ if isscalar(vars[n]):
+ vars[n]['='] = _eval_scalar(vars[n]['='], params)
+
+ for n in list(vars.keys()):
+ if n == block['name']: # n is block name
+ if 'note' in vars[n]:
+ block['note'] = vars[n]['note']
+ if block['block'] == 'function':
+ if 'result' in block and block['result'] in vars:
+ vars[n] = appenddecl(vars[n], vars[block['result']])
+ if 'prefix' in block:
+ pr = block['prefix']
+ pr1 = pr.replace('pure', '')
+ ispure = (not pr == pr1)
+ pr = pr1.replace('recursive', '')
+ isrec = (not pr == pr1)
+ m = typespattern[0].match(pr)
+ if m:
+ typespec, selector, attr, edecl = cracktypespec0(
+ m.group('this'), m.group('after'))
+ kindselect, charselect, typename = cracktypespec(
+ typespec, selector)
+ vars[n]['typespec'] = typespec
+ try:
+ if block['result']:
+ vars[block['result']]['typespec'] = typespec
+ except Exception:
+ pass
+ if kindselect:
+ if 'kind' in kindselect:
+ try:
+ kindselect['kind'] = eval(
+ kindselect['kind'], {}, params)
+ except Exception:
+ pass
+ vars[n]['kindselector'] = kindselect
+ if charselect:
+ vars[n]['charselector'] = charselect
+ if typename:
+ vars[n]['typename'] = typename
+ if ispure:
+ vars[n] = setattrspec(vars[n], 'pure')
+ if isrec:
+ vars[n] = setattrspec(vars[n], 'recursive')
+ else:
+ outmess(
+ 'analyzevars: prefix (%s) were not used\n' % repr(block['prefix']))
+ if not block['block'] in ['module', 'pythonmodule', 'python module', 'block data']:
+ if 'commonvars' in block:
+ neededvars = copy.copy(block['args'] + block['commonvars'])
+ else:
+ neededvars = copy.copy(block['args'])
+ for n in list(vars.keys()):
+ if l_or(isintent_callback, isintent_aux)(vars[n]):
+ neededvars.append(n)
+ if 'entry' in block:
+ neededvars.extend(list(block['entry'].keys()))
+ for k in list(block['entry'].keys()):
+ for n in block['entry'][k]:
+ if n not in neededvars:
+ neededvars.append(n)
+ if block['block'] == 'function':
+ if 'result' in block:
+ neededvars.append(block['result'])
+ else:
+ neededvars.append(block['name'])
+ if block['block'] in ['subroutine', 'function']:
+ name = block['name']
+ if name in vars and 'intent' in vars[name]:
+ block['intent'] = vars[name]['intent']
+ if block['block'] == 'type':
+ neededvars.extend(list(vars.keys()))
+ for n in list(vars.keys()):
+ if n not in neededvars:
+ del vars[n]
+ return vars
+
+
+analyzeargs_re_1 = re.compile(r'\A[a-z]+[\w$]*\Z', re.I)
+
+
+def param_eval(v, g_params, params, dimspec=None):
+ """
+ Creates a dictionary of indices and values for each parameter in a
+ parameter array to be evaluated later.
+
+ WARNING: It is not possible to initialize multidimensional array
+ parameters e.g. dimension(-3:1, 4, 3:5) at this point. This is because in
+ Fortran initialization through array constructor requires the RESHAPE
+ intrinsic function. Since the right-hand side of the parameter declaration
+ is not executed in f2py, but rather at the compiled c/fortran extension,
+ later, it is not possible to execute a reshape of a parameter array.
+ One issue remains: if the user wants to access the array parameter from
+ python, we should either
+ 1) allow them to access the parameter array using python standard indexing
+ (which is often incompatible with the original fortran indexing)
+ 2) allow the parameter array to be accessed in python as a dictionary with
+ fortran indices as keys
+ We are choosing 2 for now.
+ """
+ if dimspec is None:
+ try:
+ p = eval(v, g_params, params)
+ except Exception as msg:
+ p = v
+ outmess(f'param_eval: got "{msg}" on {v!r}\n')
+ return p
+
+ # This is an array parameter.
+ # First, we parse the dimension information
+ if len(dimspec) < 2 or dimspec[::len(dimspec)-1] != "()":
+ raise ValueError(f'param_eval: dimension {dimspec} can\'t be parsed')
+ dimrange = dimspec[1:-1].split(',')
+ if len(dimrange) == 1:
+ # e.g. dimension(2) or dimension(-1:1)
+ dimrange = dimrange[0].split(':')
+ # now, dimrange is a list of 1 or 2 elements
+ if len(dimrange) == 1:
+ bound = param_parse(dimrange[0], params)
+ dimrange = range(1, int(bound)+1)
+ else:
+ lbound = param_parse(dimrange[0], params)
+ ubound = param_parse(dimrange[1], params)
+ dimrange = range(int(lbound), int(ubound)+1)
+ else:
+ raise ValueError(f'param_eval: multidimensional array parameters '
+ '{dimspec} not supported')
+
+ # Parse parameter value
+ v = (v[2:-2] if v.startswith('(/') else v).split(',')
+ v_eval = []
+ for item in v:
+ try:
+ item = eval(item, g_params, params)
+ except Exception as msg:
+ outmess(f'param_eval: got "{msg}" on {item!r}\n')
+ v_eval.append(item)
+
+ p = dict(zip(dimrange, v_eval))
+
+ return p
+
+
+def param_parse(d, params):
+ """Recursively parse array dimensions.
+
+ Parses the declaration of an array variable or parameter
+ `dimension` keyword, and is called recursively if the
+ dimension for this array is a previously defined parameter
+ (found in `params`).
+
+ Parameters
+ ----------
+ d : str
+ Fortran expression describing the dimension of an array.
+ params : dict
+ Previously parsed parameters declared in the Fortran source file.
+
+ Returns
+ -------
+ out : str
+ Parsed dimension expression.
+
+ Examples
+ --------
+
+ * If the line being analyzed is
+
+ `integer, parameter, dimension(2) :: pa = (/ 3, 5 /)`
+
+ then `d = 2` and we return immediately, with
+
+ >>> d = '2'
+ >>> param_parse(d, params)
+ 2
+
+ * If the line being analyzed is
+
+ `integer, parameter, dimension(pa) :: pb = (/1, 2, 3/)`
+
+ then `d = 'pa'`; since `pa` is a previously parsed parameter,
+ and `pa = 3`, we call `param_parse` recursively, to obtain
+
+ >>> d = 'pa'
+ >>> params = {'pa': 3}
+ >>> param_parse(d, params)
+ 3
+
+ * If the line being analyzed is
+
+ `integer, parameter, dimension(pa(1)) :: pb = (/1, 2, 3/)`
+
+ then `d = 'pa(1)'`; since `pa` is a previously parsed parameter,
+ and `pa(1) = 3`, we call `param_parse` recursively, to obtain
+
+ >>> d = 'pa(1)'
+ >>> params = dict(pa={1: 3, 2: 5})
+ >>> param_parse(d, params)
+ 3
+ """
+ if "(" in d:
+ # this dimension expression is an array
+ dname = d[:d.find("(")]
+ ddims = d[d.find("(")+1:d.rfind(")")]
+ # this dimension expression is also a parameter;
+ # parse it recursively
+ index = int(param_parse(ddims, params))
+ return str(params[dname][index])
+ elif d in params:
+ return str(params[d])
+ else:
+ for p in params:
+ re_1 = re.compile(
+ r'(?P<before>.*?)\b' + p + r'\b(?P<after>.*)', re.I
+ )
+ m = re_1.match(d)
+ while m:
+ d = m.group('before') + \
+ str(params[p]) + m.group('after')
+ m = re_1.match(d)
+ return d
+
+
+def expr2name(a, block, args=[]):
+ orig_a = a
+ a_is_expr = not analyzeargs_re_1.match(a)
+ if a_is_expr: # `a` is an expression
+ implicitrules, attrrules = buildimplicitrules(block)
+ at = determineexprtype(a, block['vars'], implicitrules)
+ na = 'e_'
+ for c in a:
+ c = c.lower()
+ if c not in string.ascii_lowercase + string.digits:
+ c = '_'
+ na = na + c
+ if na[-1] == '_':
+ na = na + 'e'
+ else:
+ na = na + '_e'
+ a = na
+ while a in block['vars'] or a in block['args']:
+ a = a + 'r'
+ if a in args:
+ k = 1
+ while a + str(k) in args:
+ k = k + 1
+ a = a + str(k)
+ if a_is_expr:
+ block['vars'][a] = at
+ else:
+ if a not in block['vars']:
+ if orig_a in block['vars']:
+ block['vars'][a] = block['vars'][orig_a]
+ else:
+ block['vars'][a] = {}
+ if 'externals' in block and orig_a in block['externals'] + block['interfaced']:
+ block['vars'][a] = setattrspec(block['vars'][a], 'external')
+ return a
+
+
+def analyzeargs(block):
+ setmesstext(block)
+ implicitrules, _ = buildimplicitrules(block)
+ if 'args' not in block:
+ block['args'] = []
+ args = []
+ for a in block['args']:
+ a = expr2name(a, block, args)
+ args.append(a)
+ block['args'] = args
+ if 'entry' in block:
+ for k, args1 in list(block['entry'].items()):
+ for a in args1:
+ if a not in block['vars']:
+ block['vars'][a] = {}
+
+ for b in block['body']:
+ if b['name'] in args:
+ if 'externals' not in block:
+ block['externals'] = []
+ if b['name'] not in block['externals']:
+ block['externals'].append(b['name'])
+ if 'result' in block and block['result'] not in block['vars']:
+ block['vars'][block['result']] = {}
+ return block
+
+determineexprtype_re_1 = re.compile(r'\A\(.+?,.+?\)\Z', re.I)
+determineexprtype_re_2 = re.compile(r'\A[+-]?\d+(_(?P<name>\w+)|)\Z', re.I)
+determineexprtype_re_3 = re.compile(
+ r'\A[+-]?[\d.]+[-\d+de.]*(_(?P<name>\w+)|)\Z', re.I)
+determineexprtype_re_4 = re.compile(r'\A\(.*\)\Z', re.I)
+determineexprtype_re_5 = re.compile(r'\A(?P<name>\w+)\s*\(.*?\)\s*\Z', re.I)
+
+
+def _ensure_exprdict(r):
+ if isinstance(r, int):
+ return {'typespec': 'integer'}
+ if isinstance(r, float):
+ return {'typespec': 'real'}
+ if isinstance(r, complex):
+ return {'typespec': 'complex'}
+ if isinstance(r, dict):
+ return r
+ raise AssertionError(repr(r))
+
+
+def determineexprtype(expr, vars, rules={}):
+ if expr in vars:
+ return _ensure_exprdict(vars[expr])
+ expr = expr.strip()
+ if determineexprtype_re_1.match(expr):
+ return {'typespec': 'complex'}
+ m = determineexprtype_re_2.match(expr)
+ if m:
+ if 'name' in m.groupdict() and m.group('name'):
+ outmess(
+ 'determineexprtype: selected kind types not supported (%s)\n' % repr(expr))
+ return {'typespec': 'integer'}
+ m = determineexprtype_re_3.match(expr)
+ if m:
+ if 'name' in m.groupdict() and m.group('name'):
+ outmess(
+ 'determineexprtype: selected kind types not supported (%s)\n' % repr(expr))
+ return {'typespec': 'real'}
+ for op in ['+', '-', '*', '/']:
+ for e in [x.strip() for x in markoutercomma(expr, comma=op).split('@' + op + '@')]:
+ if e in vars:
+ return _ensure_exprdict(vars[e])
+ t = {}
+ if determineexprtype_re_4.match(expr): # in parenthesis
+ t = determineexprtype(expr[1:-1], vars, rules)
+ else:
+ m = determineexprtype_re_5.match(expr)
+ if m:
+ rn = m.group('name')
+ t = determineexprtype(m.group('name'), vars, rules)
+ if t and 'attrspec' in t:
+ del t['attrspec']
+ if not t:
+ if rn[0] in rules:
+ return _ensure_exprdict(rules[rn[0]])
+ if expr[0] in '\'"':
+ return {'typespec': 'character', 'charselector': {'*': '*'}}
+ if not t:
+ outmess(
+ 'determineexprtype: could not determine expressions (%s) type.\n' % (repr(expr)))
+ return t
+
+######
+
+
+def crack2fortrangen(block, tab='\n', as_interface=False):
+ global skipfuncs, onlyfuncs
+
+ setmesstext(block)
+ ret = ''
+ if isinstance(block, list):
+ for g in block:
+ if g and g['block'] in ['function', 'subroutine']:
+ if g['name'] in skipfuncs:
+ continue
+ if onlyfuncs and g['name'] not in onlyfuncs:
+ continue
+ ret = ret + crack2fortrangen(g, tab, as_interface=as_interface)
+ return ret
+ prefix = ''
+ name = ''
+ args = ''
+ blocktype = block['block']
+ if blocktype == 'program':
+ return ''
+ argsl = []
+ if 'name' in block:
+ name = block['name']
+ if 'args' in block:
+ vars = block['vars']
+ for a in block['args']:
+ a = expr2name(a, block, argsl)
+ if not isintent_callback(vars[a]):
+ argsl.append(a)
+ if block['block'] == 'function' or argsl:
+ args = '(%s)' % ','.join(argsl)
+ f2pyenhancements = ''
+ if 'f2pyenhancements' in block:
+ for k in list(block['f2pyenhancements'].keys()):
+ f2pyenhancements = '%s%s%s %s' % (
+ f2pyenhancements, tab + tabchar, k, block['f2pyenhancements'][k])
+ intent_lst = block.get('intent', [])[:]
+ if blocktype == 'function' and 'callback' in intent_lst:
+ intent_lst.remove('callback')
+ if intent_lst:
+ f2pyenhancements = '%s%sintent(%s) %s' %\
+ (f2pyenhancements, tab + tabchar,
+ ','.join(intent_lst), name)
+ use = ''
+ if 'use' in block:
+ use = use2fortran(block['use'], tab + tabchar)
+ common = ''
+ if 'common' in block:
+ common = common2fortran(block['common'], tab + tabchar)
+ if name == 'unknown_interface':
+ name = ''
+ result = ''
+ if 'result' in block:
+ result = ' result (%s)' % block['result']
+ if block['result'] not in argsl:
+ argsl.append(block['result'])
+ body = crack2fortrangen(block['body'], tab + tabchar, as_interface=as_interface)
+ vars = vars2fortran(
+ block, block['vars'], argsl, tab + tabchar, as_interface=as_interface)
+ mess = ''
+ if 'from' in block and not as_interface:
+ mess = '! in %s' % block['from']
+ if 'entry' in block:
+ entry_stmts = ''
+ for k, i in list(block['entry'].items()):
+ entry_stmts = '%s%sentry %s(%s)' \
+ % (entry_stmts, tab + tabchar, k, ','.join(i))
+ body = body + entry_stmts
+ if blocktype == 'block data' and name == '_BLOCK_DATA_':
+ name = ''
+ ret = '%s%s%s %s%s%s %s%s%s%s%s%s%send %s %s' % (
+ tab, prefix, blocktype, name, args, result, mess, f2pyenhancements, use, vars, common, body, tab, blocktype, name)
+ return ret
+
+
+def common2fortran(common, tab=''):
+ ret = ''
+ for k in list(common.keys()):
+ if k == '_BLNK_':
+ ret = '%s%scommon %s' % (ret, tab, ','.join(common[k]))
+ else:
+ ret = '%s%scommon /%s/ %s' % (ret, tab, k, ','.join(common[k]))
+ return ret
+
+
+def use2fortran(use, tab=''):
+ ret = ''
+ for m in list(use.keys()):
+ ret = '%s%suse %s,' % (ret, tab, m)
+ if use[m] == {}:
+ if ret and ret[-1] == ',':
+ ret = ret[:-1]
+ continue
+ if 'only' in use[m] and use[m]['only']:
+ ret = '%s only:' % (ret)
+ if 'map' in use[m] and use[m]['map']:
+ c = ' '
+ for k in list(use[m]['map'].keys()):
+ if k == use[m]['map'][k]:
+ ret = '%s%s%s' % (ret, c, k)
+ c = ','
+ else:
+ ret = '%s%s%s=>%s' % (ret, c, k, use[m]['map'][k])
+ c = ','
+ if ret and ret[-1] == ',':
+ ret = ret[:-1]
+ return ret
+
+
+def true_intent_list(var):
+ lst = var['intent']
+ ret = []
+ for intent in lst:
+ try:
+ f = globals()['isintent_%s' % intent]
+ except KeyError:
+ pass
+ else:
+ if f(var):
+ ret.append(intent)
+ return ret
+
+
+def vars2fortran(block, vars, args, tab='', as_interface=False):
+ setmesstext(block)
+ ret = ''
+ nout = []
+ for a in args:
+ if a in block['vars']:
+ nout.append(a)
+ if 'commonvars' in block:
+ for a in block['commonvars']:
+ if a in vars:
+ if a not in nout:
+ nout.append(a)
+ else:
+ errmess(
+ 'vars2fortran: Confused?!: "%s" is not defined in vars.\n' % a)
+ if 'varnames' in block:
+ nout.extend(block['varnames'])
+ if not as_interface:
+ for a in list(vars.keys()):
+ if a not in nout:
+ nout.append(a)
+ for a in nout:
+ if 'depend' in vars[a]:
+ for d in vars[a]['depend']:
+ if d in vars and 'depend' in vars[d] and a in vars[d]['depend']:
+ errmess(
+ 'vars2fortran: Warning: cross-dependence between variables "%s" and "%s"\n' % (a, d))
+ if 'externals' in block and a in block['externals']:
+ if isintent_callback(vars[a]):
+ ret = '%s%sintent(callback) %s' % (ret, tab, a)
+ ret = '%s%sexternal %s' % (ret, tab, a)
+ if isoptional(vars[a]):
+ ret = '%s%soptional %s' % (ret, tab, a)
+ if a in vars and 'typespec' not in vars[a]:
+ continue
+ cont = 1
+ for b in block['body']:
+ if a == b['name'] and b['block'] == 'function':
+ cont = 0
+ break
+ if cont:
+ continue
+ if a not in vars:
+ show(vars)
+ outmess('vars2fortran: No definition for argument "%s".\n' % a)
+ continue
+ if a == block['name']:
+ if block['block'] != 'function' or block.get('result'):
+ # 1) skip declaring a variable that name matches with
+ # subroutine name
+ # 2) skip declaring function when its type is
+ # declared via `result` construction
+ continue
+ if 'typespec' not in vars[a]:
+ if 'attrspec' in vars[a] and 'external' in vars[a]['attrspec']:
+ if a in args:
+ ret = '%s%sexternal %s' % (ret, tab, a)
+ continue
+ show(vars[a])
+ outmess('vars2fortran: No typespec for argument "%s".\n' % a)
+ continue
+ vardef = vars[a]['typespec']
+ if vardef == 'type' and 'typename' in vars[a]:
+ vardef = '%s(%s)' % (vardef, vars[a]['typename'])
+ selector = {}
+ if 'kindselector' in vars[a]:
+ selector = vars[a]['kindselector']
+ elif 'charselector' in vars[a]:
+ selector = vars[a]['charselector']
+ if '*' in selector:
+ if selector['*'] in ['*', ':']:
+ vardef = '%s*(%s)' % (vardef, selector['*'])
+ else:
+ vardef = '%s*%s' % (vardef, selector['*'])
+ else:
+ if 'len' in selector:
+ vardef = '%s(len=%s' % (vardef, selector['len'])
+ if 'kind' in selector:
+ vardef = '%s,kind=%s)' % (vardef, selector['kind'])
+ else:
+ vardef = '%s)' % (vardef)
+ elif 'kind' in selector:
+ vardef = '%s(kind=%s)' % (vardef, selector['kind'])
+ c = ' '
+ if 'attrspec' in vars[a]:
+ attr = [l for l in vars[a]['attrspec']
+ if l not in ['external']]
+ if as_interface and 'intent(in)' in attr and 'intent(out)' in attr:
+ # In Fortran, intent(in, out) are conflicting while
+ # intent(in, out) can be specified only via
+ # `!f2py intent(out) ..`.
+ # So, for the Fortran interface, we'll drop
+ # intent(out) to resolve the conflict.
+ attr.remove('intent(out)')
+ if attr:
+ vardef = '%s, %s' % (vardef, ','.join(attr))
+ c = ','
+ if 'dimension' in vars[a]:
+ vardef = '%s%sdimension(%s)' % (
+ vardef, c, ','.join(vars[a]['dimension']))
+ c = ','
+ if 'intent' in vars[a]:
+ lst = true_intent_list(vars[a])
+ if lst:
+ vardef = '%s%sintent(%s)' % (vardef, c, ','.join(lst))
+ c = ','
+ if 'check' in vars[a]:
+ vardef = '%s%scheck(%s)' % (vardef, c, ','.join(vars[a]['check']))
+ c = ','
+ if 'depend' in vars[a]:
+ vardef = '%s%sdepend(%s)' % (
+ vardef, c, ','.join(vars[a]['depend']))
+ c = ','
+ if '=' in vars[a]:
+ v = vars[a]['=']
+ if vars[a]['typespec'] in ['complex', 'double complex']:
+ try:
+ v = eval(v)
+ v = '(%s,%s)' % (v.real, v.imag)
+ except Exception:
+ pass
+ vardef = '%s :: %s=%s' % (vardef, a, v)
+ else:
+ vardef = '%s :: %s' % (vardef, a)
+ ret = '%s%s%s' % (ret, tab, vardef)
+ return ret
+######
+
+
+# We expose post_processing_hooks as global variable so that
+# user-libraries could register their own hooks to f2py.
+post_processing_hooks = []
+
+
+def crackfortran(files):
+ global usermodules, post_processing_hooks
+
+ outmess('Reading fortran codes...\n', 0)
+ readfortrancode(files, crackline)
+ outmess('Post-processing...\n', 0)
+ usermodules = []
+ postlist = postcrack(grouplist[0])
+ outmess('Applying post-processing hooks...\n', 0)
+ for hook in post_processing_hooks:
+ outmess(f' {hook.__name__}\n', 0)
+ postlist = traverse(postlist, hook)
+ outmess('Post-processing (stage 2)...\n', 0)
+ postlist = postcrack2(postlist)
+ return usermodules + postlist
+
+
+def crack2fortran(block):
+ global f2py_version
+
+ pyf = crack2fortrangen(block) + '\n'
+ header = """! -*- f90 -*-
+! Note: the context of this file is case sensitive.
+"""
+ footer = """
+! This file was auto-generated with f2py (version:%s).
+! See:
+! https://web.archive.org/web/20140822061353/http://cens.ioc.ee/projects/f2py2e
+""" % (f2py_version)
+ return header + pyf + footer
+
+
+def _is_visit_pair(obj):
+ return (isinstance(obj, tuple)
+ and len(obj) == 2
+ and isinstance(obj[0], (int, str)))
+
+
+def traverse(obj, visit, parents=[], result=None, *args, **kwargs):
+ '''Traverse f2py data structure with the following visit function:
+
+ def visit(item, parents, result, *args, **kwargs):
+ """
+
+ parents is a list of key-"f2py data structure" pairs from which
+ items are taken from.
+
+ result is a f2py data structure that is filled with the
+ return value of the visit function.
+
+ item is 2-tuple (index, value) if parents[-1][1] is a list
+ item is 2-tuple (key, value) if parents[-1][1] is a dict
+
+ The return value of visit must be None, or of the same kind as
+ item, that is, if parents[-1] is a list, the return value must
+ be 2-tuple (new_index, new_value), or if parents[-1] is a
+ dict, the return value must be 2-tuple (new_key, new_value).
+
+ If new_index or new_value is None, the return value of visit
+ is ignored, that is, it will not be added to the result.
+
+ If the return value is None, the content of obj will be
+ traversed, otherwise not.
+ """
+ '''
+
+ if _is_visit_pair(obj):
+ if obj[0] == 'parent_block':
+ # avoid infinite recursion
+ return obj
+ new_result = visit(obj, parents, result, *args, **kwargs)
+ if new_result is not None:
+ assert _is_visit_pair(new_result)
+ return new_result
+ parent = obj
+ result_key, obj = obj
+ else:
+ parent = (None, obj)
+ result_key = None
+
+ if isinstance(obj, list):
+ new_result = []
+ for index, value in enumerate(obj):
+ new_index, new_item = traverse((index, value), visit,
+ parents=parents + [parent],
+ result=result, *args, **kwargs)
+ if new_index is not None:
+ new_result.append(new_item)
+ elif isinstance(obj, dict):
+ new_result = dict()
+ for key, value in obj.items():
+ new_key, new_value = traverse((key, value), visit,
+ parents=parents + [parent],
+ result=result, *args, **kwargs)
+ if new_key is not None:
+ new_result[new_key] = new_value
+ else:
+ new_result = obj
+
+ if result_key is None:
+ return new_result
+ return result_key, new_result
+
+
+def character_backward_compatibility_hook(item, parents, result,
+ *args, **kwargs):
+ """Previously, Fortran character was incorrectly treated as
+ character*1. This hook fixes the usage of the corresponding
+ variables in `check`, `dimension`, `=`, and `callstatement`
+ expressions.
+
+ The usage of `char*` in `callprotoargument` expression can be left
+ unchanged because C `character` is C typedef of `char`, although,
+ new implementations should use `character*` in the corresponding
+ expressions.
+
+ See https://github.com/numpy/numpy/pull/19388 for more information.
+
+ """
+ parent_key, parent_value = parents[-1]
+ key, value = item
+
+ def fix_usage(varname, value):
+ value = re.sub(r'[*]\s*\b' + varname + r'\b', varname, value)
+ value = re.sub(r'\b' + varname + r'\b\s*[\[]\s*0\s*[\]]',
+ varname, value)
+ return value
+
+ if parent_key in ['dimension', 'check']:
+ assert parents[-3][0] == 'vars'
+ vars_dict = parents[-3][1]
+ elif key == '=':
+ assert parents[-2][0] == 'vars'
+ vars_dict = parents[-2][1]
+ else:
+ vars_dict = None
+
+ new_value = None
+ if vars_dict is not None:
+ new_value = value
+ for varname, vd in vars_dict.items():
+ if ischaracter(vd):
+ new_value = fix_usage(varname, new_value)
+ elif key == 'callstatement':
+ vars_dict = parents[-2][1]['vars']
+ new_value = value
+ for varname, vd in vars_dict.items():
+ if ischaracter(vd):
+ # replace all occurrences of `<varname>` with
+ # `&<varname>` in argument passing
+ new_value = re.sub(
+ r'(?<![&])\b' + varname + r'\b', '&' + varname, new_value)
+
+ if new_value is not None:
+ if new_value != value:
+ # We report the replacements here so that downstream
+ # software could update their source codes
+ # accordingly. However, such updates are recommended only
+ # when BC with numpy 1.21 or older is not required.
+ outmess(f'character_bc_hook[{parent_key}.{key}]:'
+ f' replaced `{value}` -> `{new_value}`\n', 1)
+ return (key, new_value)
+
+
+post_processing_hooks.append(character_backward_compatibility_hook)
+
+
+if __name__ == "__main__":
+ files = []
+ funcs = []
+ f = 1
+ f2 = 0
+ f3 = 0
+ showblocklist = 0
+ for l in sys.argv[1:]:
+ if l == '':
+ pass
+ elif l[0] == ':':
+ f = 0
+ elif l == '-quiet':
+ quiet = 1
+ verbose = 0
+ elif l == '-verbose':
+ verbose = 2
+ quiet = 0
+ elif l == '-fix':
+ if strictf77:
+ outmess(
+ 'Use option -f90 before -fix if Fortran 90 code is in fix form.\n', 0)
+ skipemptyends = 1
+ sourcecodeform = 'fix'
+ elif l == '-skipemptyends':
+ skipemptyends = 1
+ elif l == '--ignore-contains':
+ ignorecontains = 1
+ elif l == '-f77':
+ strictf77 = 1
+ sourcecodeform = 'fix'
+ elif l == '-f90':
+ strictf77 = 0
+ sourcecodeform = 'free'
+ skipemptyends = 1
+ elif l == '-h':
+ f2 = 1
+ elif l == '-show':
+ showblocklist = 1
+ elif l == '-m':
+ f3 = 1
+ elif l[0] == '-':
+ errmess('Unknown option %s\n' % repr(l))
+ elif f2:
+ f2 = 0
+ pyffilename = l
+ elif f3:
+ f3 = 0
+ f77modulename = l
+ elif f:
+ try:
+ open(l).close()
+ files.append(l)
+ except OSError as detail:
+ errmess(f'OSError: {detail!s}\n')
+ else:
+ funcs.append(l)
+ if not strictf77 and f77modulename and not skipemptyends:
+ outmess("""\
+ Warning: You have specified module name for non Fortran 77 code that
+ should not need one (expect if you are scanning F90 code for non
+ module blocks but then you should use flag -skipemptyends and also
+ be sure that the files do not contain programs without program
+ statement).
+""", 0)
+
+ postlist = crackfortran(files)
+ if pyffilename:
+ outmess('Writing fortran code to file %s\n' % repr(pyffilename), 0)
+ pyf = crack2fortran(postlist)
+ with open(pyffilename, 'w') as f:
+ f.write(pyf)
+ if showblocklist:
+ show(postlist)