@@ -169,47 +169,58 @@ def parse_var_keywords(test_str: str) -> tuple[list[str], str]:
169
169
170
170
def read_var_def (line : str , var_type : str = None , fun_only : bool = False ):
171
171
"""Attempt to read variable definition line"""
172
+
173
+ def parse_kind (line : str ):
174
+ match = FRegex .KIND_SPEC .match (line )
175
+ if not match :
176
+ return None , line
177
+ kind_str = match .group (1 ).replace (" " , "" )
178
+ line = line [match .end (0 ) :]
179
+ if kind_str .find ("(" ) >= 0 :
180
+ match_char = find_paren_match (line )
181
+ if match_char < 0 : # this triggers while typing with autocomplete
182
+ raise ValueError ("Incomplete kind specification" )
183
+ kind_str += line [: match_char + 1 ].strip ()
184
+ line = line [match_char + 1 :]
185
+ return kind_str , line
186
+
172
187
if var_type is None :
173
188
type_match = FRegex .VAR .match (line )
174
189
if type_match is None :
175
190
return None
176
- else :
177
- var_type = type_match .group (0 ).strip ()
178
- trailing_line = line [type_match .end (0 ) :]
191
+ var_type = type_match .group (0 ).strip ()
192
+ trailing_line = line [type_match .end (0 ) :]
179
193
else :
180
194
trailing_line = line [len (var_type ) :]
181
195
var_type = var_type .upper ()
182
196
trailing_line = trailing_line .split ("!" )[0 ]
183
197
if len (trailing_line ) == 0 :
184
198
return None
185
- #
186
- kind_match = FRegex .KIND_SPEC .match (trailing_line )
187
- if kind_match :
188
- kind_str = kind_match .group (1 ).replace (" " , "" )
189
- var_type += kind_str
190
- trailing_line = trailing_line [kind_match .end (0 ) :]
191
- if kind_str .find ("(" ) >= 0 :
192
- match_char = find_paren_match (trailing_line )
193
- if match_char < 0 :
194
- return None # Incomplete type spec
195
- else :
196
- kind_word = trailing_line [: match_char + 1 ].strip ()
197
- var_type += kind_word
198
- trailing_line = trailing_line [match_char + 1 :]
199
- else :
200
- # Class and Type statements need a kind spec
201
- if var_type in ("TYPE" , "CLASS" ):
202
- return None
203
- # Make sure next character is space or comma or colon
204
- if not trailing_line [0 ] in (" " , "," , ":" ):
205
- return None
199
+
200
+ # Parse the global kind, if any, for the current line definition
201
+ # The global kind in some cases, like characters can be overriden by a locally
202
+ # defined kind
203
+ try :
204
+ kind_str , trailing_line = parse_kind (trailing_line )
205
+ var_type += kind_str # XXX: see below
206
+ except ValueError :
207
+ return None
208
+ except TypeError : # XXX: remove with explicit kind specification in VarInfo
209
+ pass
210
+
211
+ # Class and Type statements need a kind spec
212
+ if not kind_str and var_type in ("TYPE" , "CLASS" ):
213
+ return None
214
+ # Make sure next character is space or comma or colon
215
+ if not kind_str and not trailing_line [0 ] in (" " , "," , ":" ):
216
+ return None
206
217
#
207
218
keywords , trailing_line = parse_var_keywords (trailing_line )
208
219
# Check if this is a function definition
209
220
fun_def = read_fun_def (trailing_line , ResultSig (type = var_type , keywords = keywords ))
210
- if ( fun_def is not None ) or fun_only :
221
+ if fun_def or fun_only :
211
222
return fun_def
212
- #
223
+ # Split the type and variable name
213
224
line_split = trailing_line .split ("::" )
214
225
if len (line_split ) == 1 :
215
226
if len (keywords ) > 0 :
@@ -222,8 +233,8 @@ def read_var_def(line: str, var_type: str = None, fun_only: bool = False):
222
233
var_words = separate_def_list (trailing_line .strip ())
223
234
if var_words is None :
224
235
var_words = []
225
- #
226
- return "var" , VarInfo (var_type , keywords , var_words )
236
+
237
+ return "var" , VarInfo (var_type , keywords , var_words , kind_str )
227
238
228
239
229
240
def get_procedure_modifiers (
@@ -1356,9 +1367,13 @@ def parse(
1356
1367
procedure_def = True
1357
1368
link_name = get_paren_substring (desc_string )
1358
1369
for var_name in obj_info .var_names :
1370
+ desc = desc_string
1359
1371
link_name : str = None
1360
1372
if var_name .find ("=>" ) > - 1 :
1361
1373
name_split = var_name .split ("=>" )
1374
+ # TODO: rename name_raw to name
1375
+ # TODO: rename name_stripped to name
1376
+ # TODO: rename desc_string to desc
1362
1377
name_raw = name_split [0 ]
1363
1378
link_name = name_split [1 ].split ("(" )[0 ].strip ()
1364
1379
if link_name .lower () == "null" :
@@ -1367,28 +1382,27 @@ def parse(
1367
1382
name_raw = var_name .split ("=" )[0 ]
1368
1383
# Add dimension if specified
1369
1384
# TODO: turn into function and add support for co-arrays i.e. [*]
1370
- key_tmp = obj_info .keywords [:]
1371
- iparen = name_raw .find ("(" )
1372
- if iparen == 0 :
1385
+ # Copy global keywords to the individual variable
1386
+ var_keywords : list [str ] = obj_info .keywords [:]
1387
+ # The name starts with (
1388
+ if name_raw .find ("(" ) == 0 :
1373
1389
continue
1374
- elif iparen > 0 :
1375
- if name_raw [iparen - 1 ] == "*" :
1376
- iparen -= 1
1377
- if desc_string .find ("(" ) < 0 :
1378
- desc_string += f"*({ get_paren_substring (name_raw )} )"
1379
- else :
1380
- key_tmp .append (
1381
- f"dimension({ get_paren_substring (name_raw )} )"
1382
- )
1383
- name_raw = name_raw [:iparen ]
1390
+ name_raw , dims = self .parse_imp_dim (name_raw )
1391
+ name_raw , char_len = self .parse_imp_char (name_raw )
1392
+ if dims :
1393
+ var_keywords .append (dims )
1394
+ if char_len :
1395
+ desc += char_len
1396
+
1384
1397
name_stripped = name_raw .strip ()
1385
- keywords , keyword_info = map_keywords (key_tmp )
1398
+ keywords , keyword_info = map_keywords (var_keywords )
1399
+
1386
1400
if procedure_def :
1387
1401
new_var = Method (
1388
1402
file_ast ,
1389
1403
line_no ,
1390
1404
name_stripped ,
1391
- desc_string ,
1405
+ desc ,
1392
1406
keywords ,
1393
1407
keyword_info = keyword_info ,
1394
1408
link_obj = link_name ,
@@ -1398,9 +1412,10 @@ def parse(
1398
1412
file_ast ,
1399
1413
line_no ,
1400
1414
name_stripped ,
1401
- desc_string ,
1415
+ desc ,
1402
1416
keywords ,
1403
1417
keyword_info = keyword_info ,
1418
+ # kind=obj_info.var_kind,
1404
1419
link_obj = link_name ,
1405
1420
)
1406
1421
# If the object is fortran_var and a parameter include
@@ -1413,7 +1428,7 @@ def parse(
1413
1428
new_var .set_parameter_val (var )
1414
1429
1415
1430
# Check if the "variable" is external and if so cycle
1416
- if find_external (file_ast , desc_string , name_stripped , new_var ):
1431
+ if find_external (file_ast , desc , name_stripped , new_var ):
1417
1432
continue
1418
1433
1419
1434
# if not merge_external:
@@ -1643,6 +1658,56 @@ def parse(
1643
1658
log .debug (f"{ error ['range' ]} : { error ['message' ]} " )
1644
1659
return file_ast
1645
1660
1661
+ def parse_imp_dim (self , line : str ):
1662
+ """Parse the implicit dimension of an array e.g.
1663
+ var(3,4), var_name(size(val,1)*10)
1664
+
1665
+ Parameters
1666
+ ----------
1667
+ line : str
1668
+ line containing variable name
1669
+
1670
+ Returns
1671
+ -------
1672
+ tuple[str, str]
1673
+ truncated line, dimension string
1674
+ """
1675
+ m = re .compile (r"[ ]*\w+[ ]*(\()" , re .I ).match (line )
1676
+ if not m :
1677
+ return line , None
1678
+ i = find_paren_match (line [m .end (1 ) :])
1679
+ if i < 0 :
1680
+ return line , None # triggers for autocomplete
1681
+ dims = line [m .start (1 ) : m .end (1 ) + i + 1 ]
1682
+ line = line [: m .start (1 )] + line [m .end (1 ) + i + 1 :]
1683
+ return line , f"dimension{ dims } "
1684
+
1685
+ def parse_imp_char (self , line : str ):
1686
+ """Parse the implicit character length from a variable e.g.
1687
+ var_name*10 or var_name*(10), var_name*(size(val, 1))
1688
+
1689
+ Parameters
1690
+ ----------
1691
+ line : str
1692
+ line containing potential variable
1693
+
1694
+ Returns
1695
+ -------
1696
+ tuple[str, str]
1697
+ truncated line, character length
1698
+ """
1699
+ match = re .compile (r"(\w+)[ ]*\*[ ]*(\d+|\()" , re .I ).match (line )
1700
+ if not match :
1701
+ return line , None
1702
+ if match .group (2 ) == "(" :
1703
+ i = find_paren_match (line [match .end (2 ) :])
1704
+ if i < 0 :
1705
+ return line , None # triggers for autocomplete
1706
+ char_len = line [match .start (2 ) : match .end (2 ) + i + 1 ]
1707
+ elif match .group (2 ).isdigit ():
1708
+ char_len = match .group (2 )
1709
+ return match .group (1 ), f"*{ char_len } "
1710
+
1646
1711
def parse_end_scope_word (
1647
1712
self , line : str , ln : int , file_ast : FortranAST , match : re .Match
1648
1713
) -> bool :
0 commit comments