@@ -993,6 +993,16 @@ def _resolvenameargspattern(line):
993
993
994
994
995
995
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
+ """
996
1006
global groupcounter , groupname , groupcache , grouplist , filepositiontext
997
1007
global currentfilename , f77modulename , neededinterface , neededmodule
998
1008
global expectbegin , gotnextfile , previous_context
@@ -1679,10 +1689,18 @@ def markinnerspaces(line):
1679
1689
1680
1690
1681
1691
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
+ """
1682
1699
global groupcache , groupcounter
1683
1700
1684
1701
last_name = None
1685
1702
kindselect , charselect , typename = cracktypespec (typespec , selector )
1703
+ # Clean up outer commas, whitespace and undesired chars from attrspec
1686
1704
if attrspec :
1687
1705
attrspec = [x .strip () for x in markoutercomma (attrspec ).split ('@,@' )]
1688
1706
l = []
@@ -2396,8 +2414,6 @@ def _calc_depend_dict(vars):
2396
2414
2397
2415
2398
2416
def get_sorted_names (vars ):
2399
- """
2400
- """
2401
2417
depend_dict = _calc_depend_dict (vars )
2402
2418
names = []
2403
2419
for name in list (depend_dict .keys ()):
@@ -2450,7 +2466,7 @@ def _selected_real_kind_func(p, r=0, radix=0):
2450
2466
if p < 16 :
2451
2467
return 8
2452
2468
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' )):
2454
2470
if p <= 33 :
2455
2471
return 16
2456
2472
else :
@@ -2489,6 +2505,7 @@ def get_parameters(vars, global_params={}):
2489
2505
# TODO: test .eq., .neq., etc replacements.
2490
2506
]:
2491
2507
v = v .replace (* repl )
2508
+
2492
2509
v = kind_re .sub (r'kind("\1")' , v )
2493
2510
v = selected_int_kind_re .sub (r'selected_int_kind(\1)' , v )
2494
2511
@@ -2497,14 +2514,17 @@ def get_parameters(vars, global_params={}):
2497
2514
# then we may easily remove those specifiers.
2498
2515
# However, it may be that the user uses other specifiers...(!)
2499
2516
is_replaced = False
2517
+
2500
2518
if 'kindselector' in vars [n ]:
2519
+ # Remove kind specifier (including those defined
2520
+ # by parameters)
2501
2521
if 'kind' in vars [n ]['kindselector' ]:
2502
2522
orig_v_len = len (v )
2503
2523
v = v .replace ('_' + vars [n ]['kindselector' ]['kind' ], '' )
2504
2524
# Again, this will be true if even a single specifier
2505
2525
# has been replaced, see comment above.
2506
2526
is_replaced = len (v ) < orig_v_len
2507
-
2527
+
2508
2528
if not is_replaced :
2509
2529
if not selected_kind_re .match (v ):
2510
2530
v_ = v .split ('_' )
@@ -2531,27 +2551,30 @@ def get_parameters(vars, global_params={}):
2531
2551
outmess (f'get_parameters[TODO]: '
2532
2552
f'implement evaluation of complex expression { v } \n ' )
2533
2553
2554
+ dimspec = ([s .lstrip ('dimension' ).strip ()
2555
+ for s in vars [n ]['attrspec' ]
2556
+ if s .startswith ('dimension' )] or [None ])[0 ]
2557
+
2534
2558
# Handle _dp for gh-6624
2535
2559
# Also fixes gh-20460
2536
2560
if real16pattern .search (v ):
2537
2561
v = 8
2538
2562
elif real8pattern .search (v ):
2539
2563
v = 4
2540
2564
try :
2541
- params [n ] = eval (v , g_params , params )
2542
-
2565
+ params [n ] = param_eval (v , g_params , params , dimspec = dimspec )
2543
2566
except Exception as msg :
2544
2567
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
+
2546
2570
if isstring (vars [n ]) and isinstance (params [n ], int ):
2547
2571
params [n ] = chr (params [n ])
2548
2572
nl = n .lower ()
2549
2573
if nl != n :
2550
2574
params [nl ] = params [n ]
2551
2575
else :
2552
2576
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 ' )
2555
2578
return params
2556
2579
2557
2580
@@ -2560,6 +2583,7 @@ def _eval_length(length, params):
2560
2583
return '(*)'
2561
2584
return _eval_scalar (length , params )
2562
2585
2586
+
2563
2587
_is_kind_number = re .compile (r'\d+_' ).match
2564
2588
2565
2589
@@ -2580,6 +2604,10 @@ def _eval_scalar(value, params):
2580
2604
2581
2605
2582
2606
def analyzevars (block ):
2607
+ """
2608
+ Sets correct dimension information for each variable/parameter
2609
+ """
2610
+
2583
2611
global f90modulevars
2584
2612
2585
2613
setmesstext (block )
@@ -2608,7 +2636,8 @@ def analyzevars(block):
2608
2636
svars .append (n )
2609
2637
2610
2638
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
2612
2641
dep_matches = {}
2613
2642
name_match = re .compile (r'[A-Za-z][\w$]*' ).match
2614
2643
for v in list (vars .keys ()):
@@ -2707,27 +2736,30 @@ def analyzevars(block):
2707
2736
check = None
2708
2737
if dim and 'dimension' not in vars [n ]:
2709
2738
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
2712
2743
# 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 ]
2725
2757
else :
2726
2758
dl = markoutercomma (d , ':' ).split ('@:@' )
2727
2759
if len (dl ) == 2 and '*' in dl : # e.g. dimension(5:*)
2728
2760
dl = ['*' ]
2729
2761
d = '*'
2730
- if len (dl ) == 1 and dl [0 ] != star :
2762
+ if len (dl ) == 1 and dl [0 ] != dim_char :
2731
2763
dl = ['1' , dl [0 ]]
2732
2764
if len (dl ) == 2 :
2733
2765
d1 , d2 = map (symbolic .Expr .parse , dl )
@@ -2961,9 +2993,152 @@ def compute_deps(v, deps):
2961
2993
del vars [n ]
2962
2994
return vars
2963
2995
2996
+
2964
2997
analyzeargs_re_1 = re .compile (r'\A[a-z]+[\w$]*\Z' , re .I )
2965
2998
2966
2999
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
+
2967
3142
def expr2name (a , block , args = []):
2968
3143
orig_a = a
2969
3144
a_is_expr = not analyzeargs_re_1 .match (a )
@@ -3216,11 +3391,6 @@ def true_intent_list(var):
3216
3391
3217
3392
3218
3393
def vars2fortran (block , vars , args , tab = '' , as_interface = False ):
3219
- """
3220
- TODO:
3221
- public sub
3222
- ...
3223
- """
3224
3394
setmesstext (block )
3225
3395
ret = ''
3226
3396
nout = []
0 commit comments