8000 Merge pull request #25491 from charris/backport-f2py-toplevel-hard · numpy/numpy@08dac26 · GitHub
[go: up one dir, main page]

Skip to content

Commit 08dac26

Browse files
authored
Merge pull request #25491 from charris/backport-f2py-toplevel-hard
MAINT: Update crackfortran.py and f2py2e.py from main
2 parents 296dd8b + 6b270fb commit 08dac26

File tree

2 files changed

+255
-87
lines changed

2 files changed

+255
-87
lines changed

numpy/f2py/crackfortran.py

Lines changed: 200 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,16 @@ def _resolvenameargspattern(line):
993993

994994

995995
def analyzeline(m, case, line):
996+
"""
997+
Reads each line in the input file in sequence and updates global vars.
998+
999+
Effectively reads and collects information from the input file to the
1000+
global variable groupcache, a dictionary containing info about each part
1001+
of the fortran module.
1002+
1003+
At the end of analyzeline, information is filtered into the correct dict
1004+
keys, but parameter values and dimensions are not yet interpreted.
1005+
"""
9961006
global groupcounter, groupname, groupcache, grouplist, filepositiontext
9971007
global currentfilename, f77modulename, neededinterface, neededmodule
9981008
global expectbegin, gotnextfile, previous_context
@@ -1679,10 +1689,18 @@ def markinnerspaces(line):
16791689

16801690

16811691
def updatevars(typespec, selector, attrspec, entitydecl):
1692+
"""
1693+
Returns last_name, the variable name without special chars, parenthesis
1694+
or dimension specifiers.
1695+
1696+
Alters groupcache to add the name, typespec, attrspec (and possibly value)
1697+
of current variable.
1698+
"""
16821699
global groupcache, groupcounter
16831700

16841701
last_name = None
16851702
kindselect, charselect, typename = cracktypespec(typespec, selector)
1703+
# Clean up outer commas, whitespace and undesired chars from attrspec
16861704
if attrspec:
16871705
attrspec = [x.strip() for x in markoutercomma(attrspec).split('@,@')]
16881706
l = []
@@ -2396,8 +2414,6 @@ def _calc_depend_dict(vars):
23962414

23972415

23982416
def get_sorted_names(vars):
2399-
"""
2400-
"""
24012417
depend_dict = _calc_depend_dict(vars)
24022418
names = []
24032419
for name in list(depend_dict.keys()):
@@ -2450,7 +2466,7 @@ def _selected_real_kind_func(p, r=0, radix=0):
24502466
if p < 16:
24512467
return 8
24522468
machine = platform.machine().lower()
2453-
if machine.startswith(('aarch64', 'alpha', 'arm64', 'loongarch', 'power', 'ppc', 'riscv', 's390x', 'sparc')):
2469+
if machine.startswith(('aarch64', 'alpha', 'arm64', 'loongarch', 'mips', 'power', 'ppc', 'riscv', 's390x', 'sparc')):
24542470
if p <= 33:
24552471
return 16
24562472
else:
@@ -2489,6 +2505,7 @@ def get_parameters(vars, global_params={}):
24892505
# TODO: test .eq., .neq., etc replacements.
24902506
]:
24912507
v = v.replace(*repl)
2508+
24922509
v = kind_re.sub(r'kind("\1")', v)
24932510
v = selected_int_kind_re.sub(r'selected_int_kind(\1)', v)
24942511

@@ -2497,14 +2514,17 @@ def get_parameters(vars, global_params={}):
24972514
# then we may easily remove those specifiers.
24982515
# However, it may be that the user uses other specifiers...(!)
24992516
is_replaced = False
2517+
25002518
if 'kindselector' in vars[n]:
2519+
# Remove kind specifier (including those defined
2520+
# by parameters)
25012521
if 'kind' in vars[n]['kindselector']:
25022522
orig_v_len = len(v)
25032523
v = v.replace('_' + vars[n]['kindselector']['kind'], '')
25042524
# Again, this will be true if even a single specifier
25052525
# has been replaced, see comment above.
25062526
is_replaced = len(v) < orig_v_len
2507-
2527+
25082528
if not is_replaced:
25092529
if not selected_kind_re.match(v):
25102530
v_ = v.split('_')
@@ -2531,27 +2551,30 @@ def get_parameters(vars, global_params={}):
25312551
outmess(f'get_parameters[TODO]: '
25322552
f'implement evaluation of complex expression {v}\n')
25332553

2554+
dimspec = ([s.lstrip('dimension').strip()
2555+
for s in vars[n]['attrspec']
2556+
if s.startswith('dimension')] or [None])[0]
2557+
25342558
# Handle _dp for gh-6624
25352559
# Also fixes gh-20460
25362560
if real16pattern.search(v):
25372561
v = 8
25382562
elif real8pattern.search(v):
25392563
v = 4
25402564
try:
2541-
params[n] = eval(v, g_params, params)
2542-
2565+
params[n] = param_eval(v, g_params, params, dimspec=dimspec)
25432566
except Exception as msg:
25442567
params[n] = v
2545-
outmess('get_parameters: got "%s" on %s\n' % (msg, repr(v)))
2568+
outmess(f'get_parameters: got "{msg}" on {n!r}\n')
2569+
25462570
if isstring(vars[n]) and isinstance(params[n], int):
25472571
params[n] = chr(params[n])
25482572
nl = n.lower()
25492573
if nl != n:
25502574
params[nl] = params[n]
25512575
else:
25522576
print(vars[n])
2553-
outmess(
2554-
'get_parameters:parameter %s does not have value?!\n' % (repr(n)))
2577+
outmess(f'get_parameters:parameter {n!r} does not have value?!\n')
25552578
return params
25562579

25572580

@@ -2560,6 +2583,7 @@ def _eval_length(length, params):
25602583
return '(*)'
25612584
return _eval_scalar(length, params)
25622585

2586+
25632587
_is_kind_number = re.compile(r'\d+_').match
25642588

25652589

@@ -2580,6 +2604,10 @@ def _eval_scalar(value, params):
25802604

25812605

25822606
def analyzevars(block):
2607+
"""
2608+
Sets correct dimension information for each variable/parameter
2609+
"""
2610+
25832611
global f90modulevars
25842612

25852613
setmesstext(block)
@@ -2608,7 +2636,8 @@ def analyzevars(block):
26082636
svars.append(n)
26092637

26102638
params = get_parameters(vars, get_useparameters(block))
2611-
2639+
# At this point, params are read and interpreted, but
2640+
# the params used to define vars are not yet parsed
26122641
dep_matches = {}
26132642
name_match = re.compile(r'[A-Za-z][\w$]*').match
26142643
for v in list(vars.keys()):
@@ -2707,27 +2736,30 @@ def analyzevars(block):
27072736
check = None
27082737
if dim and 'dimension' not in vars[n]:
27092738
vars[n]['dimension'] = []
2710-
for d in rmbadname([x.strip() for x in markoutercomma(dim).split('@,@')]):
2711-
star = ':' if d == ':' else '*'
2739+
for d in rmbadname(
2740+
[x.strip() for x in markoutercomma(dim).split('@,@')]
2741+
):
2742+
# d is the expression inside the dimension declaration
27122743
# Evaluate `d` with respect to params
2713-
if d in params:
2714-
d = str(params[d])
2715-
for p in params:
2716-
re_1 = re.compile(r'(?P<before>.*?)\b' + p + r'\b(?P<after>.*)', re.I)
2717-
m = re_1.match(d)
2718-
while m:
2719-
d = m.group('before') + \
2720-
str(params[p]) + m.group('after')
2721-
m = re_1.match(d)
2722-
2723-
if d == star:
2724-
dl = [star]
2744+
try:
2745+
# the dimension for this variable depends on a
2746+
# previously defined parameter
2747+
d = param_parse(d, params)
2748+
except (ValueError, IndexError, KeyError):
2749+
outmess(
2750+
('analyzevars: could not parse dimension for '
2751+
f'variable {d!r}\n')
2752+
)
2753+
2754+
dim_char = ':' if d == ':' else '*'
2755+
if d == dim_char:
2756+
dl = [dim_char]
27252757
else:
27262758
dl = markoutercomma(d, ':').split('@:@')
27272759
if len(dl) == 2 and '*' in dl: # e.g. dimension(5:*)
27282760
dl = ['*']
27292761
d = '*'
2730-
if len(dl) == 1 and dl[0] != star:
2762+
if len(dl) == 1 and dl[0] != dim_char:
27312763
dl = ['1', dl[0]]
27322764
if len(dl) == 2:
27332765
d1, d2 = map(symbolic.Expr.parse, dl)
@@ -2961,9 +2993,152 @@ def compute_deps(v, deps):
29612993
del vars[n]
29622994
return vars
29632995

2996+
29642997
analyzeargs_re_1 = re.compile(r'\A[a-z]+[\w$]*\Z', re.I)
29652998

29662999

3000+
def param_eval(v, g_params, params, dimspec=None):
3001+
"""
3002+
Creates a dictionary of indices and values for each parameter in a
3003+
parameter array to be evaluated later.
3004+
3005+
WARNING: It is not possible to initialize multidimensional array
3006+
parameters e.g. dimension(-3:1, 4, 3:5) at this point. This is because in
3007+
Fortran initialization through array constructor requires the RESHAPE
3008+
intrinsic function. Since the right-hand side of the parameter declaration
3009+
is not executed in f2py, but rather at the compiled c/fortran extension,
3010+
later, it is not possible to execute a reshape of a parameter array.
3011+
One issue remains: if the user wants to access the array parameter from
3012+
python, we should either
3013+
1) allow them to access the parameter array using python standard indexing
3014+
(which is often incompatible with the original fortran indexing)
3015+
2) allow the parameter array to be accessed in python as a dictionary with
3016+
fortran indices as keys
3017+
We are choosing 2 for now.
3018+
"""
3019+
if dimspec is None:
3020+
try:
3021+
p = eval(v, g_params, params)
3022+
except Exception as msg:
3023+
p = v
3024+
outmess(f'param_eval: got "{msg}" on {v!r}\n')
3025+
return p
3026+
3027+
# This is an array parameter.
3028+
# First, we parse the dimension information
3029+
if len(dimspec) < 2 or dimspec[::len(dimspec)-1] != "()":
3030+
raise ValueError(f'param_eval: dimension {dimspec} can\'t be parsed')
3031+
dimrange = dimspec[1:-1].split(',')
3032+
if len(dimrange) == 1:
3033+
# e.g. dimension(2) or dimension(-1:1)
3034+
dimrange = dimrange[0].split(':')
3035+
# now, dimrange is a list of 1 or 2 elements
3036+
if len(dimrange) == 1:
3037+
bound = param_parse(dimrange[0], params)
3038+
dimrange = range(1, int(bound)+1)
3039+
else:
3040+
lbound = param_parse(dimrange[0], params)
3041+
ubound = param_parse(dimrange[1], params)
3042+
dimrange = range(int(lbound), int(ubound)+1)
3043+
else:
3044+
raise ValueError(f'param_eval: multidimensional array parameters '
3045+
'{dimspec} not supported')
3046+
3047+
# Parse parameter value
3048+
v = (v[2:-2] if v.startswith('(/') else v).split(',')
3049+
v_eval = []
3050+
for item in v:
3051+
try:
3052+
item = eval(item, g_params, params)
3053+
except Exception as msg:
3054+
outmess(f'param_eval: got "{msg}" on {item!r}\n')
3055+
v_eval.append(item)
3056+
3057+
p = dict(zip(dimrange, v_eval))
3058+
3059+
return p
3060+
3061+
3062+
def param_parse(d, params):
3063+
"""Recursively parse array dimensions.
3064+
3065+
Parses the declaration of an array variable or parameter
3066+
`dimension` keyword, and is called recursively if the
3067+
dimension for this array is a previously defined parameter
3068+
(found in `params`).
3069+
3070+
Parameters
3071+
----------
3072+
d : str
3073+
Fortran expression describing the dimension of an array.
3074+
params : dict
3075+
Previously parsed parameters declared in the Fortran source file.
3076+
3077+
Returns
3078+
-------
3079+
out : str
3080+
Parsed dimension expression.
3081+
3082+
Examples
3083+
--------
3084+
3085+
* If the line being analyzed is
3086+
3087+
`integer, parameter, dimension(2) :: pa = (/ 3, 5 /)`
3088+
3089+
then `d = 2` and we return immediately, with
3090+
3091+
>>> d = '2'
3092+
>>> param_parse(d, params)
3093+
2
3094+
3095+
* If the line being analyzed is
3096+
3097+
`integer, parameter, dimension(pa) :: pb = (/1, 2, 3/)`
3098+
3099+
then `d = 'pa'`; since `pa` is a previously parsed parameter,
3100+
and `pa = 3`, we call `param_parse` recursively, to obtain
3101+
3102+
>>> d = 'pa'
3103+
>>> params = {'pa': 3}
3104+
>>> param_parse(d, params)
3105+
3
3106+
3107+
* If the line being analyzed is
3108+
3109+
`integer, parameter, dimension(pa(1)) :: pb = (/1, 2, 3/)`
3110+
3111+
then `d = 'pa(1)'`; since `pa` is a previously parsed parameter,
3112+
and `pa(1) = 3`, we call `param_parse` recursively, to obtain
3113+
3114+
>>> d = 'pa(1)'
3115+
>>> params = dict(pa={1: 3, 2: 5})
3116+
>>> param_parse(d, params)
3117+
3
3118+
"""
3119+
if "(" in d:
3120+
# this dimension expression is an array
3121+
dname = d[:d.find("(")]
3122+
ddims = d[d.find("(")+1:d.rfind(")")]
3123+
# this dimension expression is also a parameter;
3124+
# parse it recursively
3125+
index = int(param_parse(ddims, params))
3126+
return str(params[dname][index])
3127+
elif d in params:
3128+
return str(params[d])
3129+
else:
3130+
for p in params:
3131+
re_1 = re.compile(
3132+
r'(?P<before>.*?)\b' + p + r'\b(?P<after>.*)', re.I
3133+
)
3134+
m = re_1.match(d)
3135+
while m:
3136+
d = m.group('before') + \
3137+
str(params[p]) + m.group('after')
3138+
m = re_1.match(d)
3139+
return d
3140+
3141+
29673142
def expr2name(a, block, args=[]):
29683143
orig_a = a
29693144
a_is_expr = not analyzeargs_re_1.match(a)
@@ -3216,11 +3391,6 @@ def true_intent_list(var):
32163391

32173392

32183393
def vars2fortran(block, vars, args, tab='', as_interface=False):
3219-
"""
3220-
TODO:
3221-
public sub
3222-
...
3223-
"""
32243394
setmesstext(block)
32253395
ret = ''
32263396
nout = []

0 commit comments

Comments
 (0)
0