codekingpro/portable-devtools
114k
1"""
2crackfortran --- read fortran (77,90) code and extract declaration information.
3
4Copyright 1999 -- 2011 Pearu Peterson all rights reserved.
5Copyright 2011 -- present NumPy Developers.
6Permission to use, modify, and distribute this software is given under the
7terms of the NumPy License.
8
9NO WARRANTY IS EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
10
11
12Usage of crackfortran:
13======================
14Command line keys: -quiet,-verbose,-fix,-f77,-f90,-show,-h <pyffilename>
15 -m <module name for f77 routines>,--ignore-contains
16Functions: crackfortran, crack2fortran
17The following Fortran statements/constructions are supported
18(or will be if needed):
19 block data,byte,call,character,common,complex,contains,data,
20 dimension,double complex,double precision,end,external,function,
21 implicit,integer,intent,interface,intrinsic,
22 logical,module,optional,parameter,private,public,
23 program,real,(sequence?),subroutine,type,use,virtual,
24 include,pythonmodule
25Note: 'virtual' is mapped to 'dimension'.
26Note: 'implicit integer (z) static (z)' is 'implicit static (z)' (this is minor bug).
27Note: code after 'contains' will be ignored until its scope ends.
28Note: 'common' statement is extended: dimensions are moved to variable definitions
29Note: f2py directive: <commentchar>f2py<line> is read as <line>
30Note: pythonmodule is introduced to represent Python module
31
32Usage:
33 `postlist=crackfortran(files)`
34 `postlist` contains declaration information read from the list of files `files`.
35 `crack2fortran(postlist)` returns a fortran code to be saved to pyf-file
36
37 `postlist` has the following structure:
38 *** it is a list of dictionaries containing `blocks':
39 B = {'block','body','vars','parent_block'[,'name','prefix','args','result',
40 'implicit','externals','interfaced','common','sortvars',
41 'commonvars','note']}
42 B['block'] = 'interface' | 'function' | 'subroutine' | 'module' |
43 'program' | 'block data' | 'type' | 'pythonmodule' |
44 'abstract interface'
45 B['body'] --- list containing `subblocks' with the same structure as `blocks'
46 B['parent_block'] --- dictionary of a parent block:
47 C['body'][<index>]['parent_block'] is C
48 B['vars'] --- dictionary of variable definitions
49 B['sortvars'] --- dictionary of variable definitions sorted by dependence (independent first)
50 B['name'] --- name of the block (not if B['block']=='interface')
51 B['prefix'] --- prefix string (only if B['block']=='function')
52 B['args'] --- list of argument names if B['block']== 'function' | 'subroutine'
53 B['result'] --- name of the return value (only if B['block']=='function')
54 B['implicit'] --- dictionary {'a':<variable definition>,'b':...} | None
55 B['externals'] --- list of variables being external
56 B['interfaced'] --- list of variables being external and defined
57 B['common'] --- dictionary of common blocks (list of objects)
58 B['commonvars'] --- list of variables used in common blocks (dimensions are moved to variable definitions)
59 B['from'] --- string showing the 'parents' of the current block
60 B['use'] --- dictionary of modules used in current block:
61 {<modulename>:{['only':<0|1>],['map':{<local_name1>:<use_name1>,...}]}}
62 B['note'] --- list of LaTeX comments on the block
63 B['f2pyenhancements'] --- optional dictionary
64 {'threadsafe':'','fortranname':<name>,
65 'callstatement':<C-expr>|<multi-line block>,
66 'callprotoargument':<C-expr-list>,
67 'usercode':<multi-line block>|<list of multi-line blocks>,
68 'pymethoddef:<multi-line block>'
69 }
70 B['entry'] --- dictionary {entryname:argslist,..}
71 B['varnames'] --- list of variable names given in the order of reading the
72 Fortran code, useful for derived types.
73 B['saved_interface'] --- a string of scanned routine signature, defines explicit interface
74 *** Variable definition is a dictionary
75 D = B['vars'][<variable name>] =
76 {'typespec'[,'attrspec','kindselector','charselector','=','typename']}
77 D['typespec'] = 'byte' | 'character' | 'complex' | 'double complex' |
78 'double precision' | 'integer' | 'logical' | 'real' | 'type'
79 D['attrspec'] --- list of attributes (e.g. 'dimension(<arrayspec>)',
80 'external','intent(in|out|inout|hide|c|callback|cache|aligned4|aligned8|aligned16)',
81 'optional','required', etc)
82 K = D['kindselector'] = {['*','kind']} (only if D['typespec'] =
83 'complex' | 'integer' | 'logical' | 'real' )
84 C = D['charselector'] = {['*','len','kind','f2py_len']}
85 (only if D['typespec']=='character')
86 D['='] --- initialization expression string
87 D['typename'] --- name of the type if D['typespec']=='type'
88 D['dimension'] --- list of dimension bounds
89 D['intent'] --- list of intent specifications
90 D['depend'] --- list of variable names on which current variable depends on
91 D['check'] --- list of C-expressions; if C-expr returns zero, exception is raised
92 D['note'] --- list of LaTeX comments on the variable
93 *** Meaning of kind/char selectors (few examples):
94 D['typespec>']*K['*']
95 D['typespec'](kind=K['kind'])
96 character*C['*']
97 character(len=C['len'],kind=C['kind'], f2py_len=C['f2py_len'])
98 (see also fortran type declaration statement formats below)
99
100Fortran 90 type declaration statement format (F77 is subset of F90)
101====================================================================
102(Main source: IBM XL Fortran 5.1 Language Reference Manual)
103type declaration = <typespec> [[<attrspec>]::] <entitydecl>
104<typespec> = byte |
105 character[<charselector>] |
106 complex[<kindselector>] |
107 double complex |
108 double precision |
109 integer[<kindselector>] |
110 logical[<kindselector>] |
111 real[<kindselector>] |
112 type(<typename>)
113<charselector> = * <charlen> |
114 ([len=]<len>[,[kind=]<kind>]) |
115 (kind=<kind>[,len=<len>])
116<kindselector> = * <intlen> |
117 ([kind=]<kind>)
118<attrspec> = comma separated list of attributes.
119 Only the following attributes are used in
120 building up the interface:
121 external
122 (parameter --- affects '=' key)
123 optional
124 intent
125 Other attributes are ignored.
126<intentspec> = in | out | inout
127<arrayspec> = comma separated list of dimension bounds.
128<entitydecl> = <name> [[*<charlen>][(<arrayspec>)] | [(<arrayspec>)]*<charlen>]
129 [/<init_expr>/ | =<init_expr>] [,<entitydecl>]
130
131In addition, the following attributes are used: check,depend,note
132
133TODO:
134 * Apply 'parameter' attribute (e.g. 'integer parameter :: i=2' 'real x(i)'
135 -> 'real x(2)')
136 The above may be solved by creating appropriate preprocessor program, for example.
137
138"""
139import codecs
140import copy
141import fileinput
142import os
143import platform
144import re
145import string
146import sys
147from pathlib import Path
148
149try:
150 import charset_normalizer
151except ImportError:
152 charset_normalizer = None
153
154from . import __version__, symbolic
155
156# The environment provided by auxfuncs.py is needed for some calls to eval.
157# As the needed functions cannot be determined by static inspection of the
158# code, it is safest to use import * pending a major refactoring of f2py.
159from .auxfuncs import *
160
161f2py_version = __version__.version
162
163# Global flags:
164strictf77 = 1 # Ignore `!' comments unless line[0]=='!'
165sourcecodeform = 'fix' # 'fix','free'
166quiet = 0 # Be verbose if 0 (Obsolete: not used any more)
167verbose = 1 # Be quiet if 0, extra verbose if > 1.
168tabchar = 4 * ' '
169pyffilename = ''
170f77modulename = ''
171skipemptyends = 0 # for old F77 programs without 'program' statement
172ignorecontains = 1
173dolowercase = 1
174debug = []
175
176# Global variables
177beginpattern = ''
178currentfilename = ''
179expectbegin = 1
180f90modulevars = {}
181filepositiontext = ''
182gotnextfile = 1
183groupcache = None
184groupcounter = 0
185grouplist = {groupcounter: []}
186groupname = ''
187include_paths = []
188neededmodule = -1
189onlyfuncs = []
190previous_context = None
191skipblocksuntil = -1
192skipfuncs = []
193skipfunctions = []
194usermodules = []
195
196
197def reset_global_f2py_vars():
198 global groupcounter, grouplist, neededmodule, expectbegin
199 global skipblocksuntil, usermodules, f90modulevars, gotnextfile
200 global filepositiontext, currentfilename, skipfunctions, skipfuncs
201 global onlyfuncs, include_paths, previous_context
202 global strictf77, sourcecodeform, quiet, verbose, tabchar, pyffilename
203 global f77modulename, skipemptyends, ignorecontains, dolowercase, debug
204
205 # flags
206 strictf77 = 1
207 sourcecodeform = 'fix'
208 quiet = 0
209 verbose = 1
210 tabchar = 4 * ' '
211 pyffilename = ''
212 f77modulename = ''
213 skipemptyends = 0
214 ignorecontains = 1
215 dolowercase = 1
216 debug = []
217 # variables
218 groupcounter = 0
219 grouplist = {groupcounter: []}
220 neededmodule = -1
221 expectbegin = 1
222 skipblocksuntil = -1
223 usermodules = []
224 f90modulevars = {}
225 gotnextfile = 1
226 filepositiontext = ''
227 currentfilename = ''
228 skipfunctions = []
229 skipfuncs = []
230 onlyfuncs = []
231 include_paths = []
232 previous_context = None
233
234
235def outmess(line, flag=1):
236 global filepositiontext
237
238 if not verbose:
239 return
240 if not quiet:
241 if flag:
242 sys.stdout.write(filepositiontext)
243 sys.stdout.write(line)
244
245
246re._MAXCACHE = 50
247defaultimplicitrules = {}
248for c in "abcdefghopqrstuvwxyz$_":
249 defaultimplicitrules[c] = {'typespec': 'real'}
250for c in "ijklmn":
251 defaultimplicitrules[c] = {'typespec': 'integer'}
252badnames = {}
253invbadnames = {}
254for n in ['int', 'double', 'float', 'char', 'short', 'long', 'void', 'case', 'while',
255 'return', 'signed', 'unsigned', 'if', 'for', 'typedef', 'sizeof', 'union',
256 'struct', 'static', 'register', 'new', 'break', 'do', 'goto', 'switch',
257 'continue', 'else', 'inline', 'extern', 'delete', 'const', 'auto',
258 'len', 'rank', 'shape', 'index', 'slen', 'size', '_i',
259 'max', 'min',
260 'flen', 'fshape',
261 'string', 'complex_double', 'float_double', 'stdin', 'stderr', 'stdout',
262 'type', 'default']:
263 badnames[n] = n + '_bn'
264 invbadnames[n + '_bn'] = n
265
266
267def rmbadname1(name):
268 if name in badnames:
269 errmess(f'rmbadname1: Replacing "{name}" with "{badnames[name]}".\n')
270 return badnames[name]
271 return name
272
273
274def rmbadname(names):
275 return [rmbadname1(_m) for _m in names]
276
277
278def undo_rmbadname1(name):
279 if name in invbadnames:
280 errmess(f'undo_rmbadname1: Replacing "{name}" with "{invbadnames[name]}".\n')
281 return invbadnames[name]
282 return name
283
284
285def undo_rmbadname(names):
286 return [undo_rmbadname1(_m) for _m in names]
287
288
289_has_f_header = re.compile(r'-\*-\s*fortran\s*-\*-', re.I).search
290_has_f90_header = re.compile(r'-\*-\s*f90\s*-\*-', re.I).search
291_has_fix_header = re.compile(r'-\*-\s*fix\s*-\*-', re.I).search
292_free_f90_start = re.compile(r'[^c*]\s*[^\s\d\t]', re.I).match
293
294# Extensions
295COMMON_FREE_EXTENSIONS = ['.f90', '.f95', '.f03', '.f08']
296COMMON_FIXED_EXTENSIONS = ['.for', '.ftn', '.f77', '.f']
297
298
299def openhook(filename, mode):
300 """Ensures that filename is opened with correct encoding parameter.
301
302 This function uses charset_normalizer package, when available, for
303 determining the encoding of the file to be opened. When charset_normalizer
304 is not available, the function detects only UTF encodings, otherwise, ASCII
305 encoding is used as fallback.
306 """
307 # Reads in the entire file. Robust detection of encoding.
308 # Correctly handles comments or late stage unicode characters
309 # gh-22871
310 if charset_normalizer is not None:
311 encoding = charset_normalizer.from_path(filename).best().encoding
312 else:
313 # hint: install charset_normalizer for correct encoding handling
314 # No need to read the whole file for trying with startswith
315 nbytes = min(32, os.path.getsize(filename))
316 with open(filename, 'rb') as fhandle:
317 raw = fhandle.read(nbytes)
318 if raw.startswith(codecs.BOM_UTF8):
319 encoding = 'UTF-8-SIG'
320 elif raw.startswith((codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE)):
321 encoding = 'UTF-32'
322 elif raw.startswith((codecs.BOM_LE, codecs.BOM_BE)):
323 encoding = 'UTF-16'
324 else:
325 # Fallback, without charset_normalizer
326 encoding = 'ascii'
327 return open(filename, mode, encoding=encoding)
328
329
330def is_free_format(fname):
331 """Check if file is in free format Fortran."""
332 # f90 allows both fixed and free format, assuming fixed unless
333 # signs of free format are detected.
334 result = False
335 if Path(fname).suffix.lower() in COMMON_FREE_EXTENSIONS:
336 result = True
337 with openhook(fname, 'r') as fhandle:
338 line = fhandle.readline()
339 n = 15 # the number of non-comment lines to scan for hints
340 if _has_f_header(line):
341 n = 0
342 elif _has_f90_header(line):
343 n = 0
344 result = True
345 while n > 0 and line:
346 if line[0] != '!' and line.strip():
347 n -= 1
348 if (line[0] != '\t' and _free_f90_start(line[:5])) or line[-2:-1] == '&':
349 result = True
350 break
351 line = fhandle.readline()
352 return result
353
354
355# Read fortran (77,90) code
356def readfortrancode(ffile, dowithline=show, istop=1):
357 """
358 Read fortran codes from files and
359 1) Get rid of comments, line continuations, and empty lines; lower cases.
360 2) Call dowithline(line) on every line.
361 3) Recursively call itself when statement \"include '<filename>'\" is met.
362 """
363 global gotnextfile, filepositiontext, currentfilename, sourcecodeform, strictf77
364 global beginpattern, quiet, verbose, dolowercase, include_paths
365
366 if not istop:
367 saveglobals = gotnextfile, filepositiontext, currentfilename, sourcecodeform, strictf77,\
368 beginpattern, quiet, verbose, dolowercase
369 if ffile == []:
370 return
371 localdolowercase = dolowercase
372 # cont: set to True when the content of the last line read
373 # indicates statement continuation
374 cont = False
375 finalline = ''
376 ll = ''
377 includeline = re.compile(
378 r'\s*include\s*(\'|")(?P<name>[^\'"]*)(\'|")', re.I)
379 cont1 = re.compile(r'(?P<line>.*)&\s*\Z')
380 cont2 = re.compile(r'(\s*&|)(?P<line>.*)')
381 mline_mark = re.compile(r".*?'''")
382 if istop:
383 dowithline('', -1)
384 ll, l1 = '', ''
385 spacedigits = [' '] + [str(_m) for _m in range(10)]
386 filepositiontext = ''
387 fin = fileinput.FileInput(ffile, openhook=openhook)
388 while True:
389 try:
390 l = fin.readline()
391 except UnicodeDecodeError as msg:
392 raise Exception(
393 f'readfortrancode: reading {fin.filename()}#{fin.lineno()}'
394 f' failed with\n{msg}.\nIt is likely that installing charset_normalizer'
395 ' package will help f2py determine the input file encoding'
396 ' correctly.')
397 if not l:
398 break
399 if fin.isfirstline():
400 filepositiontext = ''
401 currentfilename = fin.filename()
402 gotnextfile = 1
403 l1 = l
404 strictf77 = 0
405 sourcecodeform = 'fix'
406 ext = os.path.splitext(currentfilename)[1]
407 if Path(currentfilename).suffix.lower() in COMMON_FIXED_EXTENSIONS and \
408 not (_has_f90_header(l) or _has_fix_header(l)):
409 strictf77 = 1
410 elif is_free_format(currentfilename) and not _has_fix_header(l):
411 sourcecodeform = 'free'
412 if strictf77:
413 beginpattern = beginpattern77
414 else:
415 beginpattern = beginpattern90
416 outmess('\tReading file %s (format:%s%s)\n'
417 % (repr(currentfilename), sourcecodeform,
418 (strictf77 and ',strict') or ''))
419
420 l = l.expandtabs().replace('\xa0', ' ')
421 # Get rid of newline characters
422 while not l == '':
423 if l[-1] not in "\n\r\f":
424 break
425 l = l[:-1]
426 # Do not lower for directives, gh-2547, gh-27697, gh-26681
427 is_f2py_directive = False
428 # Unconditionally remove comments
429 (l, rl) = split_by_unquoted(l, '!')
430 l += ' '
431 if rl[:5].lower() == '!f2py': # f2py directive
432 l, _ = split_by_unquoted(l + 4 * ' ' + rl[5:], '!')
433 is_f2py_directive = True
434 if l.strip() == '': # Skip empty line
435 if sourcecodeform == 'free':
436 # In free form, a statement continues in the next line
437 # that is not a comment line [3.3.2.4^1], lines with
438 # blanks are comment lines [3.3.2.3^1]. Hence, the
439 # line continuation flag must retain its state.
440 pass
441 else:
442 # In fixed form, statement continuation is determined
443 # by a non-blank character at the 6-th position. Empty
444 # line indicates a start of a new statement
445 # [3.3.3.3^1]. Hence, the line continuation flag must
446 # be reset.
447 cont = False
448 continue
449 if sourcecodeform == 'fix':
450 if l[0] in ['*', 'c', '!', 'C', '#']:
451 if l[1:5].lower() == 'f2py': # f2py directive
452 l = ' ' + l[5:]
453 is_f2py_directive = True
454 else: # Skip comment line
455 cont = False
456 is_f2py_directive = False
457 continue
458 elif strictf77:
459 if len(l) > 72:
460 l = l[:72]
461 if l[0] not in spacedigits:
462 raise Exception('readfortrancode: Found non-(space,digit) char '
463 'in the first column.\n\tAre you sure that '
464 'this code is in fix form?\n\tline=%s' % repr(l))
465
466 if (not cont or strictf77) and (len(l) > 5 and not l[5] == ' '):
467 # Continuation of a previous line
468 ll = ll + l[6:]
469 finalline = ''
470 origfinalline = ''
471 else:
472 r = cont1.match(l)
473 if r:
474 l = r.group('line') # Continuation follows ..
475 if cont:
476 ll = ll + cont2.match(l).group('line')
477 finalline = ''
478 origfinalline = ''
479 else:
480 # clean up line beginning from possible digits.
481 l = ' ' + l[5:]
482 # f2py directives are already stripped by this point
483 if localdolowercase:
484 finalline = ll.lower()
485 else:
486 finalline = ll
487 origfinalline = ll
488 ll = l
489
490 elif sourcecodeform == 'free':
491 if not cont and ext == '.pyf' and mline_mark.match(l):
492 l = l + '\n'
493 while True:
494 lc = fin.readline()
495 if not lc:
496 errmess(
497 'Unexpected end of file when reading multiline\n')
498 break
499 l = l + lc
500 if mline_mark.match(lc):
501 break
502 l = l.rstrip()
503 r = cont1.match(l)
504 if r:
505 l = r.group('line') # Continuation follows ..
506 if cont:
507 ll = ll + cont2.match(l).group('line')
508 finalline = ''
509 origfinalline = ''
510 else:
511 if localdolowercase:
512 # only skip lowering for C style constructs
513 # gh-2547, gh-27697, gh-26681, gh-28014
514 finalline = ll.lower() if not (is_f2py_directive and iscstyledirective(ll)) else ll
515 else:
516 finalline = ll
517 origfinalline = ll
518 ll = l
519 cont = (r is not None)
520 else:
521 raise ValueError(
522 f"Flag sourcecodeform must be either 'fix' or 'free': {repr(sourcecodeform)}")
523 filepositiontext = 'Line #%d in %s:"%s"\n\t' % (
524 fin.filelineno() - 1, currentfilename, l1)
525 m = includeline.match(origfinalline)
526 if m:
527 fn = m.group('name')
528 if os.path.isfile(fn):
529 readfortrancode(fn, dowithline=dowithline, istop=0)
530 else:
531 include_dirs = [
532 os.path.dirname(currentfilename)] + include_paths
533 foundfile = 0
534 for inc_dir in include_dirs:
535 fn1 = os.path.join(inc_dir, fn)
536 if os.path.isfile(fn1):
537 foundfile = 1
538 readfortrancode(fn1, dowithline=dowithline, istop=0)
539 break
540 if not foundfile:
541 outmess('readfortrancode: could not find include file %s in %s. Ignoring.\n' % (
542 repr(fn), os.pathsep.join(include_dirs)))
543 else:
544 dowithline(finalline)
545 l1 = ll
546 # Last line should never have an f2py directive anyway
547 if localdolowercase:
548 finalline = ll.lower()
549 else:
550 finalline = ll
551 origfinalline = ll
552 filepositiontext = 'Line #%d in %s:"%s"\n\t' % (
553 fin.filelineno() - 1, currentfilename, l1)
554 m = includeline.match(origfinalline)
555 if m:
556 fn = m.group('name')
557 if os.path.isfile(fn):
558 readfortrancode(fn, dowithline=dowithline, istop=0)
559 else:
560 include_dirs = [os.path.dirname(currentfilename)] + include_paths
561 foundfile = 0
562 for inc_dir in include_dirs:
563 fn1 = os.path.join(inc_dir, fn)
564 if os.path.isfile(fn1):
565 foundfile = 1
566 readfortrancode(fn1, dowithline=dowithline, istop=0)
567 break
568 if not foundfile:
569 outmess('readfortrancode: could not find include file %s in %s. Ignoring.\n' % (
570 repr(fn), os.pathsep.join(include_dirs)))
571 else:
572 dowithline(finalline)
573 filepositiontext = ''
574 fin.close()
575 if istop:
576 dowithline('', 1)
577 else:
578 gotnextfile, filepositiontext, currentfilename, sourcecodeform, strictf77,\
579 beginpattern, quiet, verbose, dolowercase = saveglobals
580
581
582# Crack line
583beforethisafter = r'\s*(?P<before>%s(?=\s*(\b(%s)\b)))'\
584 r'\s*(?P<this>(\b(%s)\b))'\
585 r'\s*(?P<after>%s)\s*\Z'
586##
587fortrantypes = r'character|logical|integer|real|complex|double\s*(precision\s*(complex|)|complex)|type(?=\s*\([\w\s,=(*)]*\))|byte'
588typespattern = re.compile(
589 beforethisafter % ('', fortrantypes, fortrantypes, '.*'), re.I), 'type'
590typespattern4implicit = re.compile(beforethisafter % (
591 '', fortrantypes + '|static|automatic|undefined', fortrantypes + '|static|automatic|undefined', '.*'), re.I)
592#
593functionpattern = re.compile(beforethisafter % (
594 r'([a-z]+[\w\s(=*+-/)]*?|)', 'function', 'function', '.*'), re.I), 'begin'
595subroutinepattern = re.compile(beforethisafter % (
596 r'[a-z\s]*?', 'subroutine', 'subroutine', '.*'), re.I), 'begin'
597# modulepattern=re.compile(beforethisafter%('[a-z\s]*?','module','module','.*'),re.I),'begin'
598#
599groupbegins77 = r'program|block\s*data'
600beginpattern77 = re.compile(
601 beforethisafter % ('', groupbegins77, groupbegins77, '.*'), re.I), 'begin'
602groupbegins90 = groupbegins77 + \
603 r'|module(?!\s*procedure)|python\s*module|(abstract|)\s*interface|'\
604 r'type(?!\s*\()'
605beginpattern90 = re.compile(
606 beforethisafter % ('', groupbegins90, groupbegins90, '.*'), re.I), 'begin'
607groupends = (r'end|endprogram|endblockdata|endmodule|endpythonmodule|'
608 r'endinterface|endsubroutine|endfunction')
609endpattern = re.compile(
610 beforethisafter % ('', groupends, groupends, '.*'), re.I), 'end'
611# block, the Fortran 2008 construct needs special handling in the rest of the file
612endifs = r'end\s*(if|do|where|select|while|forall|associate|'\
613 r'critical|enum|team)'
614endifpattern = re.compile(
615 beforethisafter % (r'[\w]*?', endifs, endifs, '.*'), re.I), 'endif'
616#
617moduleprocedures = r'module\s*procedure'
618moduleprocedurepattern = re.compile(
619 beforethisafter % ('', moduleprocedures, moduleprocedures, '.*'), re.I), \
620 'moduleprocedure'
621implicitpattern = re.compile(
622 beforethisafter % ('', 'implicit', 'implicit', '.*'), re.I), 'implicit'
623dimensionpattern = re.compile(beforethisafter % (
624 '', 'dimension|virtual', 'dimension|virtual', '.*'), re.I), 'dimension'
625externalpattern = re.compile(
626 beforethisafter % ('', 'external', 'external', '.*'), re.I), 'external'
627optionalpattern = re.compile(
628 beforethisafter % ('', 'optional', 'optional', '.*'), re.I), 'optional'
629requiredpattern = re.compile(
630 beforethisafter % ('', 'required', 'required', '.*'), re.I), 'required'
631publicpattern = re.compile(
632 beforethisafter % ('', 'public', 'public', '.*'), re.I), 'public'
633privatepattern = re.compile(
634 beforethisafter % ('', 'private', 'private', '.*'), re.I), 'private'
635intrinsicpattern = re.compile(
636 beforethisafter % ('', 'intrinsic', 'intrinsic', '.*'), re.I), 'intrinsic'
637intentpattern = re.compile(beforethisafter % (
638 '', 'intent|depend|note|check', 'intent|depend|note|check', r'\s*\(.*?\).*'), re.I), 'intent'
639parameterpattern = re.compile(
640 beforethisafter % ('', 'parameter', 'parameter', r'\s*\(.*'), re.I), 'parameter'
641datapattern = re.compile(
642 beforethisafter % ('', 'data', 'data', '.*'), re.I), 'data'
643callpattern = re.compile(
644 beforethisafter % ('', 'call', 'call', '.*'), re.I), 'call'
645entrypattern = re.compile(
646 beforethisafter % ('', 'entry', 'entry', '.*'), re.I), 'entry'
647callfunpattern = re.compile(
648 beforethisafter % ('', 'callfun', 'callfun', '.*'), re.I), 'callfun'
649commonpattern = re.compile(
650 beforethisafter % ('', 'common', 'common', '.*'), re.I), 'common'
651usepattern = re.compile(
652 beforethisafter % ('', 'use', 'use', '.*'), re.I), 'use'
653containspattern = re.compile(
654 beforethisafter % ('', 'contains', 'contains', ''), re.I), 'contains'
655formatpattern = re.compile(
656 beforethisafter % ('', 'format', 'format', '.*'), re.I), 'format'
657# Non-fortran and f2py-specific statements
658f2pyenhancementspattern = re.compile(beforethisafter % ('', 'threadsafe|fortranname|callstatement|callprotoargument|usercode|pymethoddef',
659 'threadsafe|fortranname|callstatement|callprotoargument|usercode|pymethoddef', '.*'), re.I | re.S), 'f2pyenhancements'
660multilinepattern = re.compile(
661 r"\s*(?P<before>''')(?P<this>.*?)(?P<after>''')\s*\Z", re.S), 'multiline'
662##
663
664def split_by_unquoted(line, characters):
665 """
666 Splits the line into (line[:i], line[i:]),
667 where i is the index of first occurrence of one of the characters
668 not within quotes, or len(line) if no such index exists
669 """
670 assert not (set('"\'') & set(characters)), "cannot split by unquoted quotes"
671 r = re.compile(
672 r"\A(?P<before>({single_quoted}|{double_quoted}|{not_quoted})*)"
673 r"(?P<after>{char}.*)\Z".format(
674 not_quoted=f"[^\"'{re.escape(characters)}]",
675 char=f"[{re.escape(characters)}]",
676 single_quoted=r"('([^'\\]|(\\.))*')",
677 double_quoted=r'("([^"\\]|(\\.))*")'))
678 m = r.match(line)
679 if m:
680 d = m.groupdict()
681 return (d["before"], d["after"])
682 return (line, "")
683
684def _simplifyargs(argsline):
685 a = []
686 for n in markoutercomma(argsline).split('@,@'):
687 for r in '(),':
688 n = n.replace(r, '_')
689 a.append(n)
690 return ','.join(a)
691
692
693crackline_re_1 = re.compile(r'\s*(?P<result>\b[a-z]+\w*\b)\s*=.*', re.I)
694crackline_bind_1 = re.compile(r'\s*(?P<bind>\b[a-z]+\w*\b)\s*=.*', re.I)
695crackline_bindlang = re.compile(r'\s*bind\(\s*(?P<lang>[^,]+)\s*,\s*name\s*=\s*"(?P<lang_name>[^"]+)"\s*\)', re.I)
696
697def crackline(line, reset=0):
698 """
699 reset=-1 --- initialize
700 reset=0 --- crack the line
701 reset=1 --- final check if mismatch of blocks occurred
702
703 Cracked data is saved in grouplist[0].
704 """
705 global beginpattern, groupcounter, groupname, groupcache, grouplist
706 global filepositiontext, currentfilename, neededmodule, expectbegin
707 global skipblocksuntil, skipemptyends, previous_context, gotnextfile
708
709 _, has_semicolon = split_by_unquoted(line, ";")
710 if has_semicolon and not (f2pyenhancementspattern[0].match(line) or
711 multilinepattern[0].match(line)):
712 # XXX: non-zero reset values need testing
713 assert reset == 0, repr(reset)
714 # split line on unquoted semicolons
715 line, semicolon_line = split_by_unquoted(line, ";")
716 while semicolon_line:
717 crackline(line, reset)
718 line, semicolon_line = split_by_unquoted(semicolon_line[1:], ";")
719 crackline(line, reset)
720 return
721 if reset < 0:
722 groupcounter = 0
723 groupname = {groupcounter: ''}
724 groupcache = {groupcounter: {}}
725 grouplist = {groupcounter: []}
726 groupcache[groupcounter]['body'] = []
727 groupcache[groupcounter]['vars'] = {}
728 groupcache[groupcounter]['block'] = ''
729 groupcache[groupcounter]['name'] = ''
730 neededmodule = -1
731 skipblocksuntil = -1
732 return
733 if reset > 0:
734 fl = 0
735 if f77modulename and neededmodule == groupcounter:
736 fl = 2
737 while groupcounter > fl:
738 outmess('crackline: groupcounter=%s groupname=%s\n' %
739 (repr(groupcounter), repr(groupname)))
740 outmess(
741 'crackline: Mismatch of blocks encountered. Trying to fix it by assuming "end" statement.\n')
742 grouplist[groupcounter - 1].append(groupcache[groupcounter])
743 grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
744 del grouplist[groupcounter]
745 groupcounter = groupcounter - 1
746 if f77modulename and neededmodule == groupcounter:
747 grouplist[groupcounter - 1].append(groupcache[groupcounter])
748 grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
749 del grouplist[groupcounter]
750 groupcounter = groupcounter - 1 # end interface
751 grouplist[groupcounter - 1].append(groupcache[groupcounter])
752 grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
753 del grouplist[groupcounter]
754 groupcounter = groupcounter - 1 # end module
755 neededmodule = -1
756 return
757 if line == '':
758 return
759 flag = 0
760 for pat in [dimensionpattern, externalpattern, intentpattern, optionalpattern,
761 requiredpattern,
762 parameterpattern, datapattern, publicpattern, privatepattern,
763 intrinsicpattern,
764 endifpattern, endpattern,
765 formatpattern,
766 beginpattern, functionpattern, subroutinepattern,
767 implicitpattern, typespattern, commonpattern,
768 callpattern, usepattern, containspattern,
769 entrypattern,
770 f2pyenhancementspattern,
771 multilinepattern,
772 moduleprocedurepattern
773 ]:
774 m = pat[0].match(line)
775 if m:
776 break
777 flag = flag + 1
778 if not m:
779 re_1 = crackline_re_1
780 if 0 <= skipblocksuntil <= groupcounter:
781 return
782 if 'externals' in groupcache[groupcounter]:
783 for name in groupcache[groupcounter]['externals']:
784 if name in invbadnames:
785 name = invbadnames[name]
786 if 'interfaced' in groupcache[groupcounter] and name in groupcache[groupcounter]['interfaced']:
787 continue
788 m1 = re.match(
789 r'(?P<before>[^"]*)\b%s\b\s*@\(@(?P<args>[^@]*)@\)@.*\Z' % name, markouterparen(line), re.I)
790 if m1:
791 m2 = re_1.match(m1.group('before'))
792 a = _simplifyargs(m1.group('args'))
793 if m2:
794 line = f"callfun {name}({a}) result ({m2.group('result')})"
795 else:
796 line = f'callfun {name}({a})'
797 m = callfunpattern[0].match(line)
798 if not m:
799 outmess(
800 f'crackline: could not resolve function call for line={repr(line)}.\n')
801 return
802 analyzeline(m, 'callfun', line)
803 return
804 if verbose > 1 or (verbose == 1 and currentfilename.lower().endswith('.pyf')):
805 previous_context = None
806 outmess('crackline:%d: No pattern for line\n' % (groupcounter))
807 return
808 elif pat[1] == 'end':
809 if 0 <= skipblocksuntil < groupcounter:
810 groupcounter = groupcounter - 1
811 if skipblocksuntil <= groupcounter:
812 return
813 if groupcounter <= 0:
814 raise Exception('crackline: groupcounter(=%s) is nonpositive. '
815 'Check the blocks.'
816 % (groupcounter))
817 m1 = beginpattern[0].match(line)
818 if (m1) and (not m1.group('this') == groupname[groupcounter]):
819 raise Exception('crackline: End group %s does not match with '
820 'previous Begin group %s\n\t%s' %
821 (repr(m1.group('this')), repr(groupname[groupcounter]),
822 filepositiontext)
823 )
824 if skipblocksuntil == groupcounter:
825 skipblocksuntil = -1
826 grouplist[groupcounter - 1].append(groupcache[groupcounter])
827 grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
828 del grouplist[groupcounter]
829 groupcounter = groupcounter - 1
830 if not skipemptyends:
831 expectbegin = 1
832 elif pat[1] == 'begin':
833 if 0 <= skipblocksuntil <= groupcounter:
834 groupcounter = groupcounter + 1
835 return
836 gotnextfile = 0
837 analyzeline(m, pat[1], line)
838 expectbegin = 0
839 elif pat[1] == 'endif':
840 pass
841 elif pat[1] == 'moduleprocedure':
842 analyzeline(m, pat[1], line)
843 elif pat[1] == 'contains':
844 if ignorecontains:
845 return
846 if 0 <= skipblocksuntil <= groupcounter:
847 return
848 skipblocksuntil = groupcounter
849 else:
850 if 0 <= skipblocksuntil <= groupcounter:
851 return
852 analyzeline(m, pat[1], line)
853
854
855def markouterparen(line):
856 l = ''
857 f = 0
858 for c in line:
859 if c == '(':
860 f = f + 1
861 if f == 1:
862 l = l + '@(@'
863 continue
864 elif c == ')':
865 f = f - 1
866 if f == 0:
867 l = l + '@)@'
868 continue
869 l = l + c
870 return l
871
872
873def markoutercomma(line, comma=','):
874 l = ''
875 f = 0
876 before, after = split_by_unquoted(line, comma + '()')
877 l += before
878 while after:
879 if (after[0] == comma) and (f == 0):
880 l += '@' + comma + '@'
881 else:
882 l += after[0]
883 if after[0] == '(':
884 f += 1
885 elif after[0] == ')':
886 f -= 1
887 before, after = split_by_unquoted(after[1:], comma + '()')
888 l += before
889 assert not f, repr((f, line, l))
890 return l
891
892def unmarkouterparen(line):
893 r = line.replace('@(@', '(').replace('@)@', ')')
894 return r
895
896
897def appenddecl(decl, decl2, force=1):
898 if not decl:
899 decl = {}
900 if not decl2:
901 return decl
902 if decl is decl2:
903 return decl
904 for k in list(decl2.keys()):
905 if k == 'typespec':
906 if force or k not in decl:
907 decl[k] = decl2[k]
908 elif k == 'attrspec':
909 for l in decl2[k]:
910 decl = setattrspec(decl, l, force)
911 elif k == 'kindselector':
912 decl = setkindselector(decl, decl2[k], force)
913 elif k == 'charselector':
914 decl = setcharselector(decl, decl2[k], force)
915 elif k in ['=', 'typename']:
916 if force or k not in decl:
917 decl[k] = decl2[k]
918 elif k == 'note':
919 pass
920 elif k in ['intent', 'check', 'dimension', 'optional',
921 'required', 'depend']:
922 errmess(f'appenddecl: "{k}" not implemented.\n')
923 else:
924 raise Exception('appenddecl: Unknown variable definition key: ' +
925 str(k))
926 return decl
927
928
929selectpattern = re.compile(
930 r'\s*(?P<this>(@\(@.*?@\)@|\*[\d*]+|\*\s*@\(@.*?@\)@|))(?P<after>.*)\Z', re.I)
931typedefpattern = re.compile(
932 r'(?:,(?P<attributes>[\w(),]+))?(::)?(?P<name>\b[a-z$_][\w$]*\b)'
933 r'(?:\((?P<params>[\w,]*)\))?\Z', re.I)
934nameargspattern = re.compile(
935 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)
936operatorpattern = re.compile(
937 r'\s*(?P<scheme>(operator|assignment))'
938 r'@\(@\s*(?P<name>[^)]+)\s*@\)@\s*\Z', re.I)
939callnameargspattern = re.compile(
940 r'\s*(?P<name>\b[\w$]+\b)\s*@\(@\s*(?P<args>.*)\s*@\)@\s*\Z', re.I)
941real16pattern = re.compile(
942 r'([-+]?(?:\d+(?:\.\d*)?|\d*\.\d+))[dD]((?:[-+]?\d+)?)')
943real8pattern = re.compile(
944 r'([-+]?((?:\d+(?:\.\d*)?|\d*\.\d+))[eE]((?:[-+]?\d+)?)|(\d+\.\d*))')
945
946_intentcallbackpattern = re.compile(r'intent\s*\(.*?\bcallback\b', re.I)
947
948
949def _is_intent_callback(vdecl):
950 for a in vdecl.get('attrspec', []):
951 if _intentcallbackpattern.match(a):
952 return 1
953 return 0
954
955
956def _resolvetypedefpattern(line):
957 line = ''.join(line.split()) # removes whitespace
958 m1 = typedefpattern.match(line)
959 print(line, m1)
960 if m1:
961 attrs = m1.group('attributes')
962 attrs = [a.lower() for a in attrs.split(',')] if attrs else []
963 return m1.group('name'), attrs, m1.group('params')
964 return None, [], None
965
966def parse_name_for_bind(line):
967 pattern = re.compile(r'bind\(\s*(?P<lang>[^,]+)(?:\s*,\s*name\s*=\s*["\'](?P<name>[^"\']+)["\']\s*)?\)', re.I)
968 match = pattern.search(line)
969 bind_statement = None
970 if match:
971 bind_statement = match.group(0)
972 # Remove the 'bind' construct from the line.
973 line = line[:match.start()] + line[match.end():]
974 return line, bind_statement
975
976def _resolvenameargspattern(line):
977 line, bind_cname = parse_name_for_bind(line)
978 line = markouterparen(line)
979 m1 = nameargspattern.match(line)
980 if m1:
981 return m1.group('name'), m1.group('args'), m1.group('result'), bind_cname
982 m1 = operatorpattern.match(line)
983 if m1:
984 name = m1.group('scheme') + '(' + m1.group('name') + ')'
985 return name, [], None, None
986 m1 = callnameargspattern.match(line)
987 if m1:
988 return m1.group('name'), m1.group('args'), None, None
989 return None, [], None, None
990
991
992def analyzeline(m, case, line):
993 """
994 Reads each line in the input file in sequence and updates global vars.
995
996 Effectively reads and collects information from the input file to the
997 global variable groupcache, a dictionary containing info about each part
998 of the fortran module.
999
1000 At the end of analyzeline, information is filtered into the correct dict
1001 keys, but parameter values and dimensions are not yet interpreted.
1002 """
1003 global groupcounter, groupname, groupcache, grouplist, filepositiontext
1004 global currentfilename, f77modulename, neededinterface, neededmodule
1005 global expectbegin, gotnextfile, previous_context
1006
1007 block = m.group('this')
1008 if case != 'multiline':
1009 previous_context = None
1010 if expectbegin and case not in ['begin', 'call', 'callfun', 'type'] \
1011 and not skipemptyends and groupcounter < 1:
1012 newname = os.path.basename(currentfilename).split('.')[0]
1013 outmess(
1014 f'analyzeline: no group yet. Creating program group with name "{newname}".\n')
1015 gotnextfile = 0
1016 groupcounter = groupcounter + 1
1017 groupname[groupcounter] = 'program'
1018 groupcache[groupcounter] = {}
1019 grouplist[groupcounter] = []
1020 groupcache[groupcounter]['body'] = []
1021 groupcache[groupcounter]['vars'] = {}
1022 groupcache[groupcounter]['block'] = 'program'
1023 groupcache[groupcounter]['name'] = newname
1024 groupcache[groupcounter]['from'] = 'fromsky'
1025 expectbegin = 0
1026 if case in ['begin', 'call', 'callfun']:
1027 # Crack line => block,name,args,result
1028 block = block.lower()
1029 if re.match(r'block\s*data', block, re.I):
1030 block = 'block data'
1031 elif re.match(r'python\s*module', block, re.I):
1032 block = 'python module'
1033 elif re.match(r'abstract\s*interface', block, re.I):
1034 block = 'abstract interface'
1035 if block == 'type':
1036 name, attrs, _ = _resolvetypedefpattern(m.group('after'))
1037 groupcache[groupcounter]['vars'][name] = {'attrspec': attrs}
1038 args = []
1039 result = None
1040 else:
1041 name, args, result, bindcline = _resolvenameargspattern(m.group('after'))
1042 if name is None:
1043 if block == 'block data':
1044 name = '_BLOCK_DATA_'
1045 else:
1046 name = ''
1047 if block not in ['interface', 'block data', 'abstract interface']:
1048 outmess('analyzeline: No name/args pattern found for line.\n')
1049
1050 previous_context = (block, name, groupcounter)
1051 if args:
1052 args = rmbadname([x.strip()
1053 for x in markoutercomma(args).split('@,@')])
1054 else:
1055 args = []
1056 if '' in args:
1057 while '' in args:
1058 args.remove('')
1059 outmess(
1060 'analyzeline: argument list is malformed (missing argument).\n')
1061
1062 # end of crack line => block,name,args,result
1063 needmodule = 0
1064 needinterface = 0
1065
1066 if case in ['call', 'callfun']:
1067 needinterface = 1
1068 if 'args' not in groupcache[groupcounter]:
1069 return
1070 if name not in groupcache[groupcounter]['args']:
1071 return
1072 for it in grouplist[groupcounter]:
1073 if it['name'] == name:
1074 return
1075 if name in groupcache[groupcounter]['interfaced']:
1076 return
1077 block = {'call': 'subroutine', 'callfun': 'function'}[case]
1078 if f77modulename and neededmodule == -1 and groupcounter <= 1:
1079 neededmodule = groupcounter + 2
1080 needmodule = 1
1081 if block not in ['interface', 'abstract interface']:
1082 needinterface = 1
1083 # Create new block(s)
1084 groupcounter = groupcounter + 1
1085 groupcache[groupcounter] = {}
1086 grouplist[groupcounter] = []
1087 if needmodule:
1088 if verbose > 1:
1089 outmess('analyzeline: Creating module block %s\n' %
1090 repr(f77modulename), 0)
1091 groupname[groupcounter] = 'module'
1092 groupcache[groupcounter]['block'] = 'python module'
1093 groupcache[groupcounter]['name'] = f77modulename
1094 groupcache[groupcounter]['from'] = ''
1095 groupcache[groupcounter]['body'] = []
1096 groupcache[groupcounter]['externals'] = []
1097 groupcache[groupcounter]['interfaced'] = []
1098 groupcache[groupcounter]['vars'] = {}
1099 groupcounter = groupcounter + 1
1100 groupcache[groupcounter] = {}
1101 grouplist[groupcounter] = []
1102 if needinterface:
1103 if verbose > 1:
1104 outmess('analyzeline: Creating additional interface block (groupcounter=%s).\n' % (
1105 groupcounter), 0)
1106 groupname[groupcounter] = 'interface'
1107 groupcache[groupcounter]['block'] = 'interface'
1108 groupcache[groupcounter]['name'] = 'unknown_interface'
1109 groupcache[groupcounter]['from'] = '%s:%s' % (
1110 groupcache[groupcounter - 1]['from'], groupcache[groupcounter - 1]['name'])
1111 groupcache[groupcounter]['body'] = []
1112 groupcache[groupcounter]['externals'] = []
1113 groupcache[groupcounter]['interfaced'] = []
1114 groupcache[groupcounter]['vars'] = {}
1115 groupcounter = groupcounter + 1
1116 groupcache[groupcounter] = {}
1117 grouplist[groupcounter] = []
1118 groupname[groupcounter] = block
1119 groupcache[groupcounter]['block'] = block
1120 if not name:
1121 name = 'unknown_' + block.replace(' ', '_')
1122 groupcache[groupcounter]['prefix'] = m.group('before')
1123 groupcache[groupcounter]['name'] = rmbadname1(name)
1124 groupcache[groupcounter]['result'] = result
1125 if groupcounter == 1:
1126 groupcache[groupcounter]['from'] = currentfilename
1127 elif f77modulename and groupcounter == 3:
1128 groupcache[groupcounter]['from'] = '%s:%s' % (
1129 groupcache[groupcounter - 1]['from'], currentfilename)
1130 else:
1131 groupcache[groupcounter]['from'] = '%s:%s' % (
1132 groupcache[groupcounter - 1]['from'], groupcache[groupcounter - 1]['name'])
1133 for k in list(groupcache[groupcounter].keys()):
1134 if not groupcache[groupcounter][k]:
1135 del groupcache[groupcounter][k]
1136
1137 groupcache[groupcounter]['args'] = args
1138 groupcache[groupcounter]['body'] = []
1139 groupcache[groupcounter]['externals'] = []
1140 groupcache[groupcounter]['interfaced'] = []
1141 groupcache[groupcounter]['vars'] = {}
1142 groupcache[groupcounter]['entry'] = {}
1143 # end of creation
1144 if block == 'type':
1145 groupcache[groupcounter]['varnames'] = []
1146
1147 if case in ['call', 'callfun']: # set parents variables
1148 if name not in groupcache[groupcounter - 2]['externals']:
1149 groupcache[groupcounter - 2]['externals'].append(name)
1150 groupcache[groupcounter]['vars'] = copy.deepcopy(
1151 groupcache[groupcounter - 2]['vars'])
1152 try:
1153 del groupcache[groupcounter]['vars'][name][
1154 groupcache[groupcounter]['vars'][name]['attrspec'].index('external')]
1155 except Exception:
1156 pass
1157 if block in ['function', 'subroutine']: # set global attributes
1158 # name is fortran name
1159 if bindcline:
1160 bindcdat = re.search(crackline_bindlang, bindcline)
1161 if bindcdat:
1162 groupcache[groupcounter]['bindlang'] = {name: {}}
1163 groupcache[groupcounter]['bindlang'][name]["lang"] = bindcdat.group('lang')
1164 if bindcdat.group('lang_name'):
1165 groupcache[groupcounter]['bindlang'][name]["name"] = bindcdat.group('lang_name')
1166 try:
1167 groupcache[groupcounter]['vars'][name] = appenddecl(
1168 groupcache[groupcounter]['vars'][name], groupcache[groupcounter - 2]['vars'][''])
1169 except Exception:
1170 pass
1171 if case == 'callfun': # return type
1172 if result and result in groupcache[groupcounter]['vars']:
1173 if not name == result:
1174 groupcache[groupcounter]['vars'][name] = appenddecl(
1175 groupcache[groupcounter]['vars'][name], groupcache[groupcounter]['vars'][result])
1176 # if groupcounter>1: # name is interfaced
1177 try:
1178 groupcache[groupcounter - 2]['interfaced'].append(name)
1179 except Exception:
1180 pass
1181 if block == 'function':
1182 t = typespattern[0].match(m.group('before') + ' ' + name)
1183 if t:
1184 typespec, selector, attr, edecl = cracktypespec0(
1185 t.group('this'), t.group('after'))
1186 updatevars(typespec, selector, attr, edecl)
1187
1188 if case in ['call', 'callfun']:
1189 grouplist[groupcounter - 1].append(groupcache[groupcounter])
1190 grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
1191 del grouplist[groupcounter]
1192 groupcounter = groupcounter - 1 # end routine
1193 grouplist[groupcounter - 1].append(groupcache[groupcounter])
1194 grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
1195 del grouplist[groupcounter]
1196 groupcounter = groupcounter - 1 # end interface
1197
1198 elif case == 'entry':
1199 name, args, result, _ = _resolvenameargspattern(m.group('after'))
1200 if name is not None:
