crackfortran.py 143 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725
  1. """
  2. crackfortran --- read fortran (77,90) code and extract declaration information.
  3. Copyright 1999 -- 2011 Pearu Peterson all rights reserved.
  4. Copyright 2011 -- present NumPy Developers.
  5. Permission to use, modify, and distribute this software is given under the
  6. terms of the NumPy License.
  7. NO WARRANTY IS EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
  8. Usage of crackfortran:
  9. ======================
  10. Command line keys: -quiet,-verbose,-fix,-f77,-f90,-show,-h <pyffilename>
  11. -m <module name for f77 routines>,--ignore-contains
  12. Functions: crackfortran, crack2fortran
  13. The following Fortran statements/constructions are supported
  14. (or will be if needed):
  15. block data,byte,call,character,common,complex,contains,data,
  16. dimension,double complex,double precision,end,external,function,
  17. implicit,integer,intent,interface,intrinsic,
  18. logical,module,optional,parameter,private,public,
  19. program,real,(sequence?),subroutine,type,use,virtual,
  20. include,pythonmodule
  21. Note: 'virtual' is mapped to 'dimension'.
  22. Note: 'implicit integer (z) static (z)' is 'implicit static (z)' (this is minor bug).
  23. Note: code after 'contains' will be ignored until its scope ends.
  24. Note: 'common' statement is extended: dimensions are moved to variable definitions
  25. Note: f2py directive: <commentchar>f2py<line> is read as <line>
  26. Note: pythonmodule is introduced to represent Python module
  27. Usage:
  28. `postlist=crackfortran(files)`
  29. `postlist` contains declaration information read from the list of files `files`.
  30. `crack2fortran(postlist)` returns a fortran code to be saved to pyf-file
  31. `postlist` has the following structure:
  32. *** it is a list of dictionaries containing `blocks':
  33. B = {'block','body','vars','parent_block'[,'name','prefix','args','result',
  34. 'implicit','externals','interfaced','common','sortvars',
  35. 'commonvars','note']}
  36. B['block'] = 'interface' | 'function' | 'subroutine' | 'module' |
  37. 'program' | 'block data' | 'type' | 'pythonmodule' |
  38. 'abstract interface'
  39. B['body'] --- list containing `subblocks' with the same structure as `blocks'
  40. B['parent_block'] --- dictionary of a parent block:
  41. C['body'][<index>]['parent_block'] is C
  42. B['vars'] --- dictionary of variable definitions
  43. B['sortvars'] --- dictionary of variable definitions sorted by dependence (independent first)
  44. B['name'] --- name of the block (not if B['block']=='interface')
  45. B['prefix'] --- prefix string (only if B['block']=='function')
  46. B['args'] --- list of argument names if B['block']== 'function' | 'subroutine'
  47. B['result'] --- name of the return value (only if B['block']=='function')
  48. B['implicit'] --- dictionary {'a':<variable definition>,'b':...} | None
  49. B['externals'] --- list of variables being external
  50. B['interfaced'] --- list of variables being external and defined
  51. B['common'] --- dictionary of common blocks (list of objects)
  52. B['commonvars'] --- list of variables used in common blocks (dimensions are moved to variable definitions)
  53. B['from'] --- string showing the 'parents' of the current block
  54. B['use'] --- dictionary of modules used in current block:
  55. {<modulename>:{['only':<0|1>],['map':{<local_name1>:<use_name1>,...}]}}
  56. B['note'] --- list of LaTeX comments on the block
  57. B['f2pyenhancements'] --- optional dictionary
  58. {'threadsafe':'','fortranname':<name>,
  59. 'callstatement':<C-expr>|<multi-line block>,
  60. 'callprotoargument':<C-expr-list>,
  61. 'usercode':<multi-line block>|<list of multi-line blocks>,
  62. 'pymethoddef:<multi-line block>'
  63. }
  64. B['entry'] --- dictionary {entryname:argslist,..}
  65. B['varnames'] --- list of variable names given in the order of reading the
  66. Fortran code, useful for derived types.
  67. B['saved_interface'] --- a string of scanned routine signature, defines explicit interface
  68. *** Variable definition is a dictionary
  69. D = B['vars'][<variable name>] =
  70. {'typespec'[,'attrspec','kindselector','charselector','=','typename']}
  71. D['typespec'] = 'byte' | 'character' | 'complex' | 'double complex' |
  72. 'double precision' | 'integer' | 'logical' | 'real' | 'type'
  73. D['attrspec'] --- list of attributes (e.g. 'dimension(<arrayspec>)',
  74. 'external','intent(in|out|inout|hide|c|callback|cache|aligned4|aligned8|aligned16)',
  75. 'optional','required', etc)
  76. K = D['kindselector'] = {['*','kind']} (only if D['typespec'] =
  77. 'complex' | 'integer' | 'logical' | 'real' )
  78. C = D['charselector'] = {['*','len','kind','f2py_len']}
  79. (only if D['typespec']=='character')
  80. D['='] --- initialization expression string
  81. D['typename'] --- name of the type if D['typespec']=='type'
  82. D['dimension'] --- list of dimension bounds
  83. D['intent'] --- list of intent specifications
  84. D['depend'] --- list of variable names on which current variable depends on
  85. D['check'] --- list of C-expressions; if C-expr returns zero, exception is raised
  86. D['note'] --- list of LaTeX comments on the variable
  87. *** Meaning of kind/char selectors (few examples):
  88. D['typespec>']*K['*']
  89. D['typespec'](kind=K['kind'])
  90. character*C['*']
  91. character(len=C['len'],kind=C['kind'], f2py_len=C['f2py_len'])
  92. (see also fortran type declaration statement formats below)
  93. Fortran 90 type declaration statement format (F77 is subset of F90)
  94. ====================================================================
  95. (Main source: IBM XL Fortran 5.1 Language Reference Manual)
  96. type declaration = <typespec> [[<attrspec>]::] <entitydecl>
  97. <typespec> = byte |
  98. character[<charselector>] |
  99. complex[<kindselector>] |
  100. double complex |
  101. double precision |
  102. integer[<kindselector>] |
  103. logical[<kindselector>] |
  104. real[<kindselector>] |
  105. type(<typename>)
  106. <charselector> = * <charlen> |
  107. ([len=]<len>[,[kind=]<kind>]) |
  108. (kind=<kind>[,len=<len>])
  109. <kindselector> = * <intlen> |
  110. ([kind=]<kind>)
  111. <attrspec> = comma separated list of attributes.
  112. Only the following attributes are used in
  113. building up the interface:
  114. external
  115. (parameter --- affects '=' key)
  116. optional
  117. intent
  118. Other attributes are ignored.
  119. <intentspec> = in | out | inout
  120. <arrayspec> = comma separated list of dimension bounds.
  121. <entitydecl> = <name> [[*<charlen>][(<arrayspec>)] | [(<arrayspec>)]*<charlen>]
  122. [/<init_expr>/ | =<init_expr>] [,<entitydecl>]
  123. In addition, the following attributes are used: check,depend,note
  124. TODO:
  125. * Apply 'parameter' attribute (e.g. 'integer parameter :: i=2' 'real x(i)'
  126. -> 'real x(2)')
  127. The above may be solved by creating appropriate preprocessor program, for example.
  128. """
  129. import codecs
  130. import copy
  131. import fileinput
  132. import os
  133. import platform
  134. import re
  135. import string
  136. import sys
  137. from pathlib import Path
  138. try:
  139. import charset_normalizer
  140. except ImportError:
  141. charset_normalizer = None
  142. from . import __version__, symbolic
  143. # The environment provided by auxfuncs.py is needed for some calls to eval.
  144. # As the needed functions cannot be determined by static inspection of the
  145. # code, it is safest to use import * pending a major refactoring of f2py.
  146. from .auxfuncs import *
  147. f2py_version = __version__.version
  148. # Global flags:
  149. strictf77 = 1 # Ignore `!' comments unless line[0]=='!'
  150. sourcecodeform = 'fix' # 'fix','free'
  151. quiet = 0 # Be verbose if 0 (Obsolete: not used any more)
  152. verbose = 1 # Be quiet if 0, extra verbose if > 1.
  153. tabchar = 4 * ' '
  154. pyffilename = ''
  155. f77modulename = ''
  156. skipemptyends = 0 # for old F77 programs without 'program' statement
  157. ignorecontains = 1
  158. dolowercase = 1
  159. debug = []
  160. # Global variables
  161. beginpattern = ''
  162. currentfilename = ''
  163. expectbegin = 1
  164. f90modulevars = {}
  165. filepositiontext = ''
  166. gotnextfile = 1
  167. groupcache = None
  168. groupcounter = 0
  169. grouplist = {groupcounter: []}
  170. groupname = ''
  171. include_paths = []
  172. neededmodule = -1
  173. onlyfuncs = []
  174. previous_context = None
  175. skipblocksuntil = -1
  176. skipfuncs = []
  177. skipfunctions = []
  178. usermodules = []
  179. def reset_global_f2py_vars():
  180. global groupcounter, grouplist, neededmodule, expectbegin
  181. global skipblocksuntil, usermodules, f90modulevars, gotnextfile
  182. global filepositiontext, currentfilename, skipfunctions, skipfuncs
  183. global onlyfuncs, include_paths, previous_context
  184. global strictf77, sourcecodeform, quiet, verbose, tabchar, pyffilename
  185. global f77modulename, skipemptyends, ignorecontains, dolowercase, debug
  186. # flags
  187. strictf77 = 1
  188. sourcecodeform = 'fix'
  189. quiet = 0
  190. verbose = 1
  191. tabchar = 4 * ' '
  192. pyffilename = ''
  193. f77modulename = ''
  194. skipemptyends = 0
  195. ignorecontains = 1
  196. dolowercase = 1
  197. debug = []
  198. # variables
  199. groupcounter = 0
  200. grouplist = {groupcounter: []}
  201. neededmodule = -1
  202. expectbegin = 1
  203. skipblocksuntil = -1
  204. usermodules = []
  205. f90modulevars = {}
  206. gotnextfile = 1
  207. filepositiontext = ''
  208. currentfilename = ''
  209. skipfunctions = []
  210. skipfuncs = []
  211. onlyfuncs = []
  212. include_paths = []
  213. previous_context = None
  214. def outmess(line, flag=1):
  215. global filepositiontext
  216. if not verbose:
  217. return
  218. if not quiet:
  219. if flag:
  220. sys.stdout.write(filepositiontext)
  221. sys.stdout.write(line)
  222. re._MAXCACHE = 50
  223. defaultimplicitrules = {}
  224. for c in "abcdefghopqrstuvwxyz$_":
  225. defaultimplicitrules[c] = {'typespec': 'real'}
  226. for c in "ijklmn":
  227. defaultimplicitrules[c] = {'typespec': 'integer'}
  228. badnames = {}
  229. invbadnames = {}
  230. for n in ['int', 'double', 'float', 'char', 'short', 'long', 'void', 'case', 'while',
  231. 'return', 'signed', 'unsigned', 'if', 'for', 'typedef', 'sizeof', 'union',
  232. 'struct', 'static', 'register', 'new', 'break', 'do', 'goto', 'switch',
  233. 'continue', 'else', 'inline', 'extern', 'delete', 'const', 'auto',
  234. 'len', 'rank', 'shape', 'index', 'slen', 'size', '_i',
  235. 'max', 'min',
  236. 'flen', 'fshape',
  237. 'string', 'complex_double', 'float_double', 'stdin', 'stderr', 'stdout',
  238. 'type', 'default']:
  239. badnames[n] = n + '_bn'
  240. invbadnames[n + '_bn'] = n
  241. def rmbadname1(name):
  242. if name in badnames:
  243. errmess(f'rmbadname1: Replacing "{name}" with "{badnames[name]}".\n')
  244. return badnames[name]
  245. return name
  246. def rmbadname(names):
  247. return [rmbadname1(_m) for _m in names]
  248. def undo_rmbadname1(name):
  249. if name in invbadnames:
  250. errmess(f'undo_rmbadname1: Replacing "{name}" with "{invbadnames[name]}".\n')
  251. return invbadnames[name]
  252. return name
  253. def undo_rmbadname(names):
  254. return [undo_rmbadname1(_m) for _m in names]
  255. _has_f_header = re.compile(r'-\*-\s*fortran\s*-\*-', re.I).search
  256. _has_f90_header = re.compile(r'-\*-\s*f90\s*-\*-', re.I).search
  257. _has_fix_header = re.compile(r'-\*-\s*fix\s*-\*-', re.I).search
  258. _free_f90_start = re.compile(r'[^c*]\s*[^\s\d\t]', re.I).match
  259. # Extensions
  260. COMMON_FREE_EXTENSIONS = ['.f90', '.f95', '.f03', '.f08']
  261. COMMON_FIXED_EXTENSIONS = ['.for', '.ftn', '.f77', '.f']
  262. def openhook(filename, mode):
  263. """Ensures that filename is opened with correct encoding parameter.
  264. This function uses charset_normalizer package, when available, for
  265. determining the encoding of the file to be opened. When charset_normalizer
  266. is not available, the function detects only UTF encodings, otherwise, ASCII
  267. encoding is used as fallback.
  268. """
  269. # Reads in the entire file. Robust detection of encoding.
  270. # Correctly handles comments or late stage unicode characters
  271. # gh-22871
  272. if charset_normalizer is not None:
  273. encoding = charset_normalizer.from_path(filename).best().encoding
  274. else:
  275. # hint: install charset_normalizer for correct encoding handling
  276. # No need to read the whole file for trying with startswith
  277. nbytes = min(32, os.path.getsize(filename))
  278. with open(filename, 'rb') as fhandle:
  279. raw = fhandle.read(nbytes)
  280. if raw.startswith(codecs.BOM_UTF8):
  281. encoding = 'UTF-8-SIG'
  282. elif raw.startswith((codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE)):
  283. encoding = 'UTF-32'
  284. elif raw.startswith((codecs.BOM_LE, codecs.BOM_BE)):
  285. encoding = 'UTF-16'
  286. else:
  287. # Fallback, without charset_normalizer
  288. encoding = 'ascii'
  289. return open(filename, mode, encoding=encoding)
  290. def is_free_format(fname):
  291. """Check if file is in free format Fortran."""
  292. # f90 allows both fixed and free format, assuming fixed unless
  293. # signs of free format are detected.
  294. result = False
  295. if Path(fname).suffix.lower() in COMMON_FREE_EXTENSIONS:
  296. result = True
  297. with openhook(fname, 'r') as fhandle:
  298. line = fhandle.readline()
  299. n = 15 # the number of non-comment lines to scan for hints
  300. if _has_f_header(line):
  301. n = 0
  302. elif _has_f90_header(line):
  303. n = 0
  304. result = True
  305. while n > 0 and line:
  306. if line[0] != '!' and line.strip():
  307. n -= 1
  308. if (line[0] != '\t' and _free_f90_start(line[:5])) or line[-2:-1] == '&':
  309. result = True
  310. break
  311. line = fhandle.readline()
  312. return result
  313. # Read fortran (77,90) code
  314. def readfortrancode(ffile, dowithline=show, istop=1):
  315. """
  316. Read fortran codes from files and
  317. 1) Get rid of comments, line continuations, and empty lines; lower cases.
  318. 2) Call dowithline(line) on every line.
  319. 3) Recursively call itself when statement \"include '<filename>'\" is met.
  320. """
  321. global gotnextfile, filepositiontext, currentfilename, sourcecodeform, strictf77
  322. global beginpattern, quiet, verbose, dolowercase, include_paths
  323. if not istop:
  324. saveglobals = gotnextfile, filepositiontext, currentfilename, sourcecodeform, strictf77,\
  325. beginpattern, quiet, verbose, dolowercase
  326. if ffile == []:
  327. return
  328. localdolowercase = dolowercase
  329. # cont: set to True when the content of the last line read
  330. # indicates statement continuation
  331. cont = False
  332. finalline = ''
  333. ll = ''
  334. includeline = re.compile(
  335. r'\s*include\s*(\'|")(?P<name>[^\'"]*)(\'|")', re.I)
  336. cont1 = re.compile(r'(?P<line>.*)&\s*\Z')
  337. cont2 = re.compile(r'(\s*&|)(?P<line>.*)')
  338. mline_mark = re.compile(r".*?'''")
  339. if istop:
  340. dowithline('', -1)
  341. ll, l1 = '', ''
  342. spacedigits = [' '] + [str(_m) for _m in range(10)]
  343. filepositiontext = ''
  344. fin = fileinput.FileInput(ffile, openhook=openhook)
  345. while True:
  346. try:
  347. l = fin.readline()
  348. except UnicodeDecodeError as msg:
  349. raise Exception(
  350. f'readfortrancode: reading {fin.filename()}#{fin.lineno()}'
  351. f' failed with\n{msg}.\nIt is likely that installing charset_normalizer'
  352. ' package will help f2py determine the input file encoding'
  353. ' correctly.')
  354. if not l:
  355. break
  356. if fin.isfirstline():
  357. filepositiontext = ''
  358. currentfilename = fin.filename()
  359. gotnextfile = 1
  360. l1 = l
  361. strictf77 = 0
  362. sourcecodeform = 'fix'
  363. ext = os.path.splitext(currentfilename)[1]
  364. if Path(currentfilename).suffix.lower() in COMMON_FIXED_EXTENSIONS and \
  365. not (_has_f90_header(l) or _has_fix_header(l)):
  366. strictf77 = 1
  367. elif is_free_format(currentfilename) and not _has_fix_header(l):
  368. sourcecodeform = 'free'
  369. if strictf77:
  370. beginpattern = beginpattern77
  371. else:
  372. beginpattern = beginpattern90
  373. outmess('\tReading file %s (format:%s%s)\n'
  374. % (repr(currentfilename), sourcecodeform,
  375. (strictf77 and ',strict') or ''))
  376. l = l.expandtabs().replace('\xa0', ' ')
  377. # Get rid of newline characters
  378. while not l == '':
  379. if l[-1] not in "\n\r\f":
  380. break
  381. l = l[:-1]
  382. # Do not lower for directives, gh-2547, gh-27697, gh-26681
  383. is_f2py_directive = False
  384. # Unconditionally remove comments
  385. (l, rl) = split_by_unquoted(l, '!')
  386. l += ' '
  387. if rl[:5].lower() == '!f2py': # f2py directive
  388. l, _ = split_by_unquoted(l + 4 * ' ' + rl[5:], '!')
  389. is_f2py_directive = True
  390. if l.strip() == '': # Skip empty line
  391. if sourcecodeform == 'free':
  392. # In free form, a statement continues in the next line
  393. # that is not a comment line [3.3.2.4^1], lines with
  394. # blanks are comment lines [3.3.2.3^1]. Hence, the
  395. # line continuation flag must retain its state.
  396. pass
  397. else:
  398. # In fixed form, statement continuation is determined
  399. # by a non-blank character at the 6-th position. Empty
  400. # line indicates a start of a new statement
  401. # [3.3.3.3^1]. Hence, the line continuation flag must
  402. # be reset.
  403. cont = False
  404. continue
  405. if sourcecodeform == 'fix':
  406. if l[0] in ['*', 'c', '!', 'C', '#']:
  407. if l[1:5].lower() == 'f2py': # f2py directive
  408. l = ' ' + l[5:]
  409. is_f2py_directive = True
  410. else: # Skip comment line
  411. cont = False
  412. is_f2py_directive = False
  413. continue
  414. elif strictf77:
  415. if len(l) > 72:
  416. l = l[:72]
  417. if l[0] not in spacedigits:
  418. raise Exception('readfortrancode: Found non-(space,digit) char '
  419. 'in the first column.\n\tAre you sure that '
  420. 'this code is in fix form?\n\tline=%s' % repr(l))
  421. if (not cont or strictf77) and (len(l) > 5 and not l[5] == ' '):
  422. # Continuation of a previous line
  423. ll = ll + l[6:]
  424. finalline = ''
  425. origfinalline = ''
  426. else:
  427. r = cont1.match(l)
  428. if r:
  429. l = r.group('line') # Continuation follows ..
  430. if cont:
  431. ll = ll + cont2.match(l).group('line')
  432. finalline = ''
  433. origfinalline = ''
  434. else:
  435. # clean up line beginning from possible digits.
  436. l = ' ' + l[5:]
  437. # f2py directives are already stripped by this point
  438. if localdolowercase:
  439. finalline = ll.lower()
  440. else:
  441. finalline = ll
  442. origfinalline = ll
  443. ll = l
  444. elif sourcecodeform == 'free':
  445. if not cont and ext == '.pyf' and mline_mark.match(l):
  446. l = l + '\n'
  447. while True:
  448. lc = fin.readline()
  449. if not lc:
  450. errmess(
  451. 'Unexpected end of file when reading multiline\n')
  452. break
  453. l = l + lc
  454. if mline_mark.match(lc):
  455. break
  456. l = l.rstrip()
  457. r = cont1.match(l)
  458. if r:
  459. l = r.group('line') # Continuation follows ..
  460. if cont:
  461. ll = ll + cont2.match(l).group('line')
  462. finalline = ''
  463. origfinalline = ''
  464. else:
  465. if localdolowercase:
  466. # only skip lowering for C style constructs
  467. # gh-2547, gh-27697, gh-26681, gh-28014
  468. finalline = ll.lower() if not (is_f2py_directive and iscstyledirective(ll)) else ll
  469. else:
  470. finalline = ll
  471. origfinalline = ll
  472. ll = l
  473. cont = (r is not None)
  474. else:
  475. raise ValueError(
  476. f"Flag sourcecodeform must be either 'fix' or 'free': {repr(sourcecodeform)}")
  477. filepositiontext = 'Line #%d in %s:"%s"\n\t' % (
  478. fin.filelineno() - 1, currentfilename, l1)
  479. m = includeline.match(origfinalline)
  480. if m:
  481. fn = m.group('name')
  482. if os.path.isfile(fn):
  483. readfortrancode(fn, dowithline=dowithline, istop=0)
  484. else:
  485. include_dirs = [
  486. os.path.dirname(currentfilename)] + include_paths
  487. foundfile = 0
  488. for inc_dir in include_dirs:
  489. fn1 = os.path.join(inc_dir, fn)
  490. if os.path.isfile(fn1):
  491. foundfile = 1
  492. readfortrancode(fn1, dowithline=dowithline, istop=0)
  493. break
  494. if not foundfile:
  495. outmess('readfortrancode: could not find include file %s in %s. Ignoring.\n' % (
  496. repr(fn), os.pathsep.join(include_dirs)))
  497. else:
  498. dowithline(finalline)
  499. l1 = ll
  500. # Last line should never have an f2py directive anyway
  501. if localdolowercase:
  502. finalline = ll.lower()
  503. else:
  504. finalline = ll
  505. origfinalline = ll
  506. filepositiontext = 'Line #%d in %s:"%s"\n\t' % (
  507. fin.filelineno() - 1, currentfilename, l1)
  508. m = includeline.match(origfinalline)
  509. if m:
  510. fn = m.group('name')
  511. if os.path.isfile(fn):
  512. readfortrancode(fn, dowithline=dowithline, istop=0)
  513. else:
  514. include_dirs = [os.path.dirname(currentfilename)] + include_paths
  515. foundfile = 0
  516. for inc_dir in include_dirs:
  517. fn1 = os.path.join(inc_dir, fn)
  518. if os.path.isfile(fn1):
  519. foundfile = 1
  520. readfortrancode(fn1, dowithline=dowithline, istop=0)
  521. break
  522. if not foundfile:
  523. outmess('readfortrancode: could not find include file %s in %s. Ignoring.\n' % (
  524. repr(fn), os.pathsep.join(include_dirs)))
  525. else:
  526. dowithline(finalline)
  527. filepositiontext = ''
  528. fin.close()
  529. if istop:
  530. dowithline('', 1)
  531. else:
  532. gotnextfile, filepositiontext, currentfilename, sourcecodeform, strictf77,\
  533. beginpattern, quiet, verbose, dolowercase = saveglobals
  534. # Crack line
  535. beforethisafter = r'\s*(?P<before>%s(?=\s*(\b(%s)\b)))'\
  536. r'\s*(?P<this>(\b(%s)\b))'\
  537. r'\s*(?P<after>%s)\s*\Z'
  538. ##
  539. fortrantypes = r'character|logical|integer|real|complex|double\s*(precision\s*(complex|)|complex)|type(?=\s*\([\w\s,=(*)]*\))|byte'
  540. typespattern = re.compile(
  541. beforethisafter % ('', fortrantypes, fortrantypes, '.*'), re.I), 'type'
  542. typespattern4implicit = re.compile(beforethisafter % (
  543. '', fortrantypes + '|static|automatic|undefined', fortrantypes + '|static|automatic|undefined', '.*'), re.I)
  544. #
  545. functionpattern = re.compile(beforethisafter % (
  546. r'([a-z]+[\w\s(=*+-/)]*?|)', 'function', 'function', '.*'), re.I), 'begin'
  547. subroutinepattern = re.compile(beforethisafter % (
  548. r'[a-z\s]*?', 'subroutine', 'subroutine', '.*'), re.I), 'begin'
  549. # modulepattern=re.compile(beforethisafter%('[a-z\s]*?','module','module','.*'),re.I),'begin'
  550. #
  551. groupbegins77 = r'program|block\s*data'
  552. beginpattern77 = re.compile(
  553. beforethisafter % ('', groupbegins77, groupbegins77, '.*'), re.I), 'begin'
  554. groupbegins90 = groupbegins77 + \
  555. r'|module(?!\s*procedure)|python\s*module|(abstract|)\s*interface|'\
  556. r'type(?!\s*\()'
  557. beginpattern90 = re.compile(
  558. beforethisafter % ('', groupbegins90, groupbegins90, '.*'), re.I), 'begin'
  559. groupends = (r'end|endprogram|endblockdata|endmodule|endpythonmodule|'
  560. r'endinterface|endsubroutine|endfunction')
  561. endpattern = re.compile(
  562. beforethisafter % ('', groupends, groupends, '.*'), re.I), 'end'
  563. # block, the Fortran 2008 construct needs special handling in the rest of the file
  564. endifs = r'end\s*(if|do|where|select|while|forall|associate|'\
  565. r'critical|enum|team)'
  566. endifpattern = re.compile(
  567. beforethisafter % (r'[\w]*?', endifs, endifs, '.*'), re.I), 'endif'
  568. #
  569. moduleprocedures = r'module\s*procedure'
  570. moduleprocedurepattern = re.compile(
  571. beforethisafter % ('', moduleprocedures, moduleprocedures, '.*'), re.I), \
  572. 'moduleprocedure'
  573. implicitpattern = re.compile(
  574. beforethisafter % ('', 'implicit', 'implicit', '.*'), re.I), 'implicit'
  575. dimensionpattern = re.compile(beforethisafter % (
  576. '', 'dimension|virtual', 'dimension|virtual', '.*'), re.I), 'dimension'
  577. externalpattern = re.compile(
  578. beforethisafter % ('', 'external', 'external', '.*'), re.I), 'external'
  579. optionalpattern = re.compile(
  580. beforethisafter % ('', 'optional', 'optional', '.*'), re.I), 'optional'
  581. requiredpattern = re.compile(
  582. beforethisafter % ('', 'required', 'required', '.*'), re.I), 'required'
  583. publicpattern = re.compile(
  584. beforethisafter % ('', 'public', 'public', '.*'), re.I), 'public'
  585. privatepattern = re.compile(
  586. beforethisafter % ('', 'private', 'private', '.*'), re.I), 'private'
  587. intrinsicpattern = re.compile(
  588. beforethisafter % ('', 'intrinsic', 'intrinsic', '.*'), re.I), 'intrinsic'
  589. intentpattern = re.compile(beforethisafter % (
  590. '', 'intent|depend|note|check', 'intent|depend|note|check', r'\s*\(.*?\).*'), re.I), 'intent'
  591. parameterpattern = re.compile(
  592. beforethisafter % ('', 'parameter', 'parameter', r'\s*\(.*'), re.I), 'parameter'
  593. datapattern = re.compile(
  594. beforethisafter % ('', 'data', 'data', '.*'), re.I), 'data'
  595. callpattern = re.compile(
  596. beforethisafter % ('', 'call', 'call', '.*'), re.I), 'call'
  597. entrypattern = re.compile(
  598. beforethisafter % ('', 'entry', 'entry', '.*'), re.I), 'entry'
  599. callfunpattern = re.compile(
  600. beforethisafter % ('', 'callfun', 'callfun', '.*'), re.I), 'callfun'
  601. commonpattern = re.compile(
  602. beforethisafter % ('', 'common', 'common', '.*'), re.I), 'common'
  603. usepattern = re.compile(
  604. beforethisafter % ('', 'use', 'use', '.*'), re.I), 'use'
  605. containspattern = re.compile(
  606. beforethisafter % ('', 'contains', 'contains', ''), re.I), 'contains'
  607. formatpattern = re.compile(
  608. beforethisafter % ('', 'format', 'format', '.*'), re.I), 'format'
  609. # Non-fortran and f2py-specific statements
  610. f2pyenhancementspattern = re.compile(beforethisafter % ('', 'threadsafe|fortranname|callstatement|callprotoargument|usercode|pymethoddef',
  611. 'threadsafe|fortranname|callstatement|callprotoargument|usercode|pymethoddef', '.*'), re.I | re.S), 'f2pyenhancements'
  612. multilinepattern = re.compile(
  613. r"\s*(?P<before>''')(?P<this>.*?)(?P<after>''')\s*\Z", re.S), 'multiline'
  614. ##
  615. def split_by_unquoted(line, characters):
  616. """
  617. Splits the line into (line[:i], line[i:]),
  618. where i is the index of first occurrence of one of the characters
  619. not within quotes, or len(line) if no such index exists
  620. """
  621. assert not (set('"\'') & set(characters)), "cannot split by unquoted quotes"
  622. r = re.compile(
  623. r"\A(?P<before>({single_quoted}|{double_quoted}|{not_quoted})*)"
  624. r"(?P<after>{char}.*)\Z".format(
  625. not_quoted=f"[^\"'{re.escape(characters)}]",
  626. char=f"[{re.escape(characters)}]",
  627. single_quoted=r"('([^'\\]|(\\.))*')",
  628. double_quoted=r'("([^"\\]|(\\.))*")'))
  629. m = r.match(line)
  630. if m:
  631. d = m.groupdict()
  632. return (d["before"], d["after"])
  633. return (line, "")
  634. def _simplifyargs(argsline):
  635. a = []
  636. for n in markoutercomma(argsline).split('@,@'):
  637. for r in '(),':
  638. n = n.replace(r, '_')
  639. a.append(n)
  640. return ','.join(a)
  641. crackline_re_1 = re.compile(r'\s*(?P<result>\b[a-z]+\w*\b)\s*=.*', re.I)
  642. crackline_bind_1 = re.compile(r'\s*(?P<bind>\b[a-z]+\w*\b)\s*=.*', re.I)
  643. crackline_bindlang = re.compile(r'\s*bind\(\s*(?P<lang>[^,]+)\s*,\s*name\s*=\s*"(?P<lang_name>[^"]+)"\s*\)', re.I)
  644. def crackline(line, reset=0):
  645. """
  646. reset=-1 --- initialize
  647. reset=0 --- crack the line
  648. reset=1 --- final check if mismatch of blocks occurred
  649. Cracked data is saved in grouplist[0].
  650. """
  651. global beginpattern, groupcounter, groupname, groupcache, grouplist
  652. global filepositiontext, currentfilename, neededmodule, expectbegin
  653. global skipblocksuntil, skipemptyends, previous_context, gotnextfile
  654. _, has_semicolon = split_by_unquoted(line, ";")
  655. if has_semicolon and not (f2pyenhancementspattern[0].match(line) or
  656. multilinepattern[0].match(line)):
  657. # XXX: non-zero reset values need testing
  658. assert reset == 0, repr(reset)
  659. # split line on unquoted semicolons
  660. line, semicolon_line = split_by_unquoted(line, ";")
  661. while semicolon_line:
  662. crackline(line, reset)
  663. line, semicolon_line = split_by_unquoted(semicolon_line[1:], ";")
  664. crackline(line, reset)
  665. return
  666. if reset < 0:
  667. groupcounter = 0
  668. groupname = {groupcounter: ''}
  669. groupcache = {groupcounter: {}}
  670. grouplist = {groupcounter: []}
  671. groupcache[groupcounter]['body'] = []
  672. groupcache[groupcounter]['vars'] = {}
  673. groupcache[groupcounter]['block'] = ''
  674. groupcache[groupcounter]['name'] = ''
  675. neededmodule = -1
  676. skipblocksuntil = -1
  677. return
  678. if reset > 0:
  679. fl = 0
  680. if f77modulename and neededmodule == groupcounter:
  681. fl = 2
  682. while groupcounter > fl:
  683. outmess('crackline: groupcounter=%s groupname=%s\n' %
  684. (repr(groupcounter), repr(groupname)))
  685. outmess(
  686. 'crackline: Mismatch of blocks encountered. Trying to fix it by assuming "end" statement.\n')
  687. grouplist[groupcounter - 1].append(groupcache[groupcounter])
  688. grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
  689. del grouplist[groupcounter]
  690. groupcounter = groupcounter - 1
  691. if f77modulename and neededmodule == groupcounter:
  692. grouplist[groupcounter - 1].append(groupcache[groupcounter])
  693. grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
  694. del grouplist[groupcounter]
  695. groupcounter = groupcounter - 1 # end interface
  696. grouplist[groupcounter - 1].append(groupcache[groupcounter])
  697. grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
  698. del grouplist[groupcounter]
  699. groupcounter = groupcounter - 1 # end module
  700. neededmodule = -1
  701. return
  702. if line == '':
  703. return
  704. flag = 0
  705. for pat in [dimensionpattern, externalpattern, intentpattern, optionalpattern,
  706. requiredpattern,
  707. parameterpattern, datapattern, publicpattern, privatepattern,
  708. intrinsicpattern,
  709. endifpattern, endpattern,
  710. formatpattern,
  711. beginpattern, functionpattern, subroutinepattern,
  712. implicitpattern, typespattern, commonpattern,
  713. callpattern, usepattern, containspattern,
  714. entrypattern,
  715. f2pyenhancementspattern,
  716. multilinepattern,
  717. moduleprocedurepattern
  718. ]:
  719. m = pat[0].match(line)
  720. if m:
  721. break
  722. flag = flag + 1
  723. if not m:
  724. re_1 = crackline_re_1
  725. if 0 <= skipblocksuntil <= groupcounter:
  726. return
  727. if 'externals' in groupcache[groupcounter]:
  728. for name in groupcache[groupcounter]['externals']:
  729. if name in invbadnames:
  730. name = invbadnames[name]
  731. if 'interfaced' in groupcache[groupcounter] and name in groupcache[groupcounter]['interfaced']:
  732. continue
  733. m1 = re.match(
  734. r'(?P<before>[^"]*)\b%s\b\s*@\(@(?P<args>[^@]*)@\)@.*\Z' % name, markouterparen(line), re.I)
  735. if m1:
  736. m2 = re_1.match(m1.group('before'))
  737. a = _simplifyargs(m1.group('args'))
  738. if m2:
  739. line = f"callfun {name}({a}) result ({m2.group('result')})"
  740. else:
  741. line = f'callfun {name}({a})'
  742. m = callfunpattern[0].match(line)
  743. if not m:
  744. outmess(
  745. f'crackline: could not resolve function call for line={repr(line)}.\n')
  746. return
  747. analyzeline(m, 'callfun', line)
  748. return
  749. if verbose > 1 or (verbose == 1 and currentfilename.lower().endswith('.pyf')):
  750. previous_context = None
  751. outmess('crackline:%d: No pattern for line\n' % (groupcounter))
  752. return
  753. elif pat[1] == 'end':
  754. if 0 <= skipblocksuntil < groupcounter:
  755. groupcounter = groupcounter - 1
  756. if skipblocksuntil <= groupcounter:
  757. return
  758. if groupcounter <= 0:
  759. raise Exception('crackline: groupcounter(=%s) is nonpositive. '
  760. 'Check the blocks.'
  761. % (groupcounter))
  762. m1 = beginpattern[0].match(line)
  763. if (m1) and (not m1.group('this') == groupname[groupcounter]):
  764. raise Exception('crackline: End group %s does not match with '
  765. 'previous Begin group %s\n\t%s' %
  766. (repr(m1.group('this')), repr(groupname[groupcounter]),
  767. filepositiontext)
  768. )
  769. if skipblocksuntil == groupcounter:
  770. skipblocksuntil = -1
  771. grouplist[groupcounter - 1].append(groupcache[groupcounter])
  772. grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
  773. del grouplist[groupcounter]
  774. groupcounter = groupcounter - 1
  775. if not skipemptyends:
  776. expectbegin = 1
  777. elif pat[1] == 'begin':
  778. if 0 <= skipblocksuntil <= groupcounter:
  779. groupcounter = groupcounter + 1
  780. return
  781. gotnextfile = 0
  782. analyzeline(m, pat[1], line)
  783. expectbegin = 0
  784. elif pat[1] == 'endif':
  785. pass
  786. elif pat[1] == 'moduleprocedure':
  787. analyzeline(m, pat[1], line)
  788. elif pat[1] == 'contains':
  789. if ignorecontains:
  790. return
  791. if 0 <= skipblocksuntil <= groupcounter:
  792. return
  793. skipblocksuntil = groupcounter
  794. else:
  795. if 0 <= skipblocksuntil <= groupcounter:
  796. return
  797. analyzeline(m, pat[1], line)
  798. def markouterparen(line):
  799. l = ''
  800. f = 0
  801. for c in line:
  802. if c == '(':
  803. f = f + 1
  804. if f == 1:
  805. l = l + '@(@'
  806. continue
  807. elif c == ')':
  808. f = f - 1
  809. if f == 0:
  810. l = l + '@)@'
  811. continue
  812. l = l + c
  813. return l
  814. def markoutercomma(line, comma=','):
  815. l = ''
  816. f = 0
  817. before, after = split_by_unquoted(line, comma + '()')
  818. l += before
  819. while after:
  820. if (after[0] == comma) and (f == 0):
  821. l += '@' + comma + '@'
  822. else:
  823. l += after[0]
  824. if after[0] == '(':
  825. f += 1
  826. elif after[0] == ')':
  827. f -= 1
  828. before, after = split_by_unquoted(after[1:], comma + '()')
  829. l += before
  830. assert not f, repr((f, line, l))
  831. return l
  832. def unmarkouterparen(line):
  833. r = line.replace('@(@', '(').replace('@)@', ')')
  834. return r
  835. def appenddecl(decl, decl2, force=1):
  836. if not decl:
  837. decl = {}
  838. if not decl2:
  839. return decl
  840. if decl is decl2:
  841. return decl
  842. for k in list(decl2.keys()):
  843. if k == 'typespec':
  844. if force or k not in decl:
  845. decl[k] = decl2[k]
  846. elif k == 'attrspec':
  847. for l in decl2[k]:
  848. decl = setattrspec(decl, l, force)
  849. elif k == 'kindselector':
  850. decl = setkindselector(decl, decl2[k], force)
  851. elif k == 'charselector':
  852. decl = setcharselector(decl, decl2[k], force)
  853. elif k in ['=', 'typename']:
  854. if force or k not in decl:
  855. decl[k] = decl2[k]
  856. elif k == 'note':
  857. pass
  858. elif k in ['intent', 'check', 'dimension', 'optional',
  859. 'required', 'depend']:
  860. errmess(f'appenddecl: "{k}" not implemented.\n')
  861. else:
  862. raise Exception('appenddecl: Unknown variable definition key: ' +
  863. str(k))
  864. return decl
  865. selectpattern = re.compile(
  866. r'\s*(?P<this>(@\(@.*?@\)@|\*[\d*]+|\*\s*@\(@.*?@\)@|))(?P<after>.*)\Z', re.I)
  867. typedefpattern = re.compile(
  868. r'(?:,(?P<attributes>[\w(),]+))?(::)?(?P<name>\b[a-z$_][\w$]*\b)'
  869. r'(?:\((?P<params>[\w,]*)\))?\Z', re.I)
  870. nameargspattern = re.compile(
  871. 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)
  872. operatorpattern = re.compile(
  873. r'\s*(?P<scheme>(operator|assignment))'
  874. r'@\(@\s*(?P<name>[^)]+)\s*@\)@\s*\Z', re.I)
  875. callnameargspattern = re.compile(
  876. r'\s*(?P<name>\b[\w$]+\b)\s*@\(@\s*(?P<args>.*)\s*@\)@\s*\Z', re.I)
  877. real16pattern = re.compile(
  878. r'([-+]?(?:\d+(?:\.\d*)?|\d*\.\d+))[dD]((?:[-+]?\d+)?)')
  879. real8pattern = re.compile(
  880. r'([-+]?((?:\d+(?:\.\d*)?|\d*\.\d+))[eE]((?:[-+]?\d+)?)|(\d+\.\d*))')
  881. _intentcallbackpattern = re.compile(r'intent\s*\(.*?\bcallback\b', re.I)
  882. def _is_intent_callback(vdecl):
  883. for a in vdecl.get('attrspec', []):
  884. if _intentcallbackpattern.match(a):
  885. return 1
  886. return 0
  887. def _resolvetypedefpattern(line):
  888. line = ''.join(line.split()) # removes whitespace
  889. m1 = typedefpattern.match(line)
  890. print(line, m1)
  891. if m1:
  892. attrs = m1.group('attributes')
  893. attrs = [a.lower() for a in attrs.split(',')] if attrs else []
  894. return m1.group('name'), attrs, m1.group('params')
  895. return None, [], None
  896. def parse_name_for_bind(line):
  897. pattern = re.compile(r'bind\(\s*(?P<lang>[^,]+)(?:\s*,\s*name\s*=\s*["\'](?P<name>[^"\']+)["\']\s*)?\)', re.I)
  898. match = pattern.search(line)
  899. bind_statement = None
  900. if match:
  901. bind_statement = match.group(0)
  902. # Remove the 'bind' construct from the line.
  903. line = line[:match.start()] + line[match.end():]
  904. return line, bind_statement
  905. def _resolvenameargspattern(line):
  906. line, bind_cname = parse_name_for_bind(line)
  907. line = markouterparen(line)
  908. m1 = nameargspattern.match(line)
  909. if m1:
  910. return m1.group('name'), m1.group('args'), m1.group('result'), bind_cname
  911. m1 = operatorpattern.match(line)
  912. if m1:
  913. name = m1.group('scheme') + '(' + m1.group('name') + ')'
  914. return name, [], None, None
  915. m1 = callnameargspattern.match(line)
  916. if m1:
  917. return m1.group('name'), m1.group('args'), None, None
  918. return None, [], None, None
  919. def analyzeline(m, case, line):
  920. """
  921. Reads each line in the input file in sequence and updates global vars.
  922. Effectively reads and collects information from the input file to the
  923. global variable groupcache, a dictionary containing info about each part
  924. of the fortran module.
  925. At the end of analyzeline, information is filtered into the correct dict
  926. keys, but parameter values and dimensions are not yet interpreted.
  927. """
  928. global groupcounter, groupname, groupcache, grouplist, filepositiontext
  929. global currentfilename, f77modulename, neededinterface, neededmodule
  930. global expectbegin, gotnextfile, previous_context
  931. block = m.group('this')
  932. if case != 'multiline':
  933. previous_context = None
  934. if expectbegin and case not in ['begin', 'call', 'callfun', 'type'] \
  935. and not skipemptyends and groupcounter < 1:
  936. newname = os.path.basename(currentfilename).split('.')[0]
  937. outmess(
  938. f'analyzeline: no group yet. Creating program group with name "{newname}".\n')
  939. gotnextfile = 0
  940. groupcounter = groupcounter + 1
  941. groupname[groupcounter] = 'program'
  942. groupcache[groupcounter] = {}
  943. grouplist[groupcounter] = []
  944. groupcache[groupcounter]['body'] = []
  945. groupcache[groupcounter]['vars'] = {}
  946. groupcache[groupcounter]['block'] = 'program'
  947. groupcache[groupcounter]['name'] = newname
  948. groupcache[groupcounter]['from'] = 'fromsky'
  949. expectbegin = 0
  950. if case in ['begin', 'call', 'callfun']:
  951. # Crack line => block,name,args,result
  952. block = block.lower()
  953. if re.match(r'block\s*data', block, re.I):
  954. block = 'block data'
  955. elif re.match(r'python\s*module', block, re.I):
  956. block = 'python module'
  957. elif re.match(r'abstract\s*interface', block, re.I):
  958. block = 'abstract interface'
  959. if block == 'type':
  960. name, attrs, _ = _resolvetypedefpattern(m.group('after'))
  961. groupcache[groupcounter]['vars'][name] = {'attrspec': attrs}
  962. args = []
  963. result = None
  964. else:
  965. name, args, result, bindcline = _resolvenameargspattern(m.group('after'))
  966. if name is None:
  967. if block == 'block data':
  968. name = '_BLOCK_DATA_'
  969. else:
  970. name = ''
  971. if block not in ['interface', 'block data', 'abstract interface']:
  972. outmess('analyzeline: No name/args pattern found for line.\n')
  973. previous_context = (block, name, groupcounter)
  974. if args:
  975. args = rmbadname([x.strip()
  976. for x in markoutercomma(args).split('@,@')])
  977. else:
  978. args = []
  979. if '' in args:
  980. while '' in args:
  981. args.remove('')
  982. outmess(
  983. 'analyzeline: argument list is malformed (missing argument).\n')
  984. # end of crack line => block,name,args,result
  985. needmodule = 0
  986. needinterface = 0
  987. if case in ['call', 'callfun']:
  988. needinterface = 1
  989. if 'args' not in groupcache[groupcounter]:
  990. return
  991. if name not in groupcache[groupcounter]['args']:
  992. return
  993. for it in grouplist[groupcounter]:
  994. if it['name'] == name:
  995. return
  996. if name in groupcache[groupcounter]['interfaced']:
  997. return
  998. block = {'call': 'subroutine', 'callfun': 'function'}[case]
  999. if f77modulename and neededmodule == -1 and groupcounter <= 1:
  1000. neededmodule = groupcounter + 2
  1001. needmodule = 1
  1002. if block not in ['interface', 'abstract interface']:
  1003. needinterface = 1
  1004. # Create new block(s)
  1005. groupcounter = groupcounter + 1
  1006. groupcache[groupcounter] = {}
  1007. grouplist[groupcounter] = []
  1008. if needmodule:
  1009. if verbose > 1:
  1010. outmess('analyzeline: Creating module block %s\n' %
  1011. repr(f77modulename), 0)
  1012. groupname[groupcounter] = 'module'
  1013. groupcache[groupcounter]['block'] = 'python module'
  1014. groupcache[groupcounter]['name'] = f77modulename
  1015. groupcache[groupcounter]['from'] = ''
  1016. groupcache[groupcounter]['body'] = []
  1017. groupcache[groupcounter]['externals'] = []
  1018. groupcache[groupcounter]['interfaced'] = []
  1019. groupcache[groupcounter]['vars'] = {}
  1020. groupcounter = groupcounter + 1
  1021. groupcache[groupcounter] = {}
  1022. grouplist[groupcounter] = []
  1023. if needinterface:
  1024. if verbose > 1:
  1025. outmess('analyzeline: Creating additional interface block (groupcounter=%s).\n' % (
  1026. groupcounter), 0)
  1027. groupname[groupcounter] = 'interface'
  1028. groupcache[groupcounter]['block'] = 'interface'
  1029. groupcache[groupcounter]['name'] = 'unknown_interface'
  1030. groupcache[groupcounter]['from'] = '%s:%s' % (
  1031. groupcache[groupcounter - 1]['from'], groupcache[groupcounter - 1]['name'])
  1032. groupcache[groupcounter]['body'] = []
  1033. groupcache[groupcounter]['externals'] = []
  1034. groupcache[groupcounter]['interfaced'] = []
  1035. groupcache[groupcounter]['vars'] = {}
  1036. groupcounter = groupcounter + 1
  1037. groupcache[groupcounter] = {}
  1038. grouplist[groupcounter] = []
  1039. groupname[groupcounter] = block
  1040. groupcache[groupcounter]['block'] = block
  1041. if not name:
  1042. name = 'unknown_' + block.replace(' ', '_')
  1043. groupcache[groupcounter]['prefix'] = m.group('before')
  1044. groupcache[groupcounter]['name'] = rmbadname1(name)
  1045. groupcache[groupcounter]['result'] = result
  1046. if groupcounter == 1:
  1047. groupcache[groupcounter]['from'] = currentfilename
  1048. elif f77modulename and groupcounter == 3:
  1049. groupcache[groupcounter]['from'] = '%s:%s' % (
  1050. groupcache[groupcounter - 1]['from'], currentfilename)
  1051. else:
  1052. groupcache[groupcounter]['from'] = '%s:%s' % (
  1053. groupcache[groupcounter - 1]['from'], groupcache[groupcounter - 1]['name'])
  1054. for k in list(groupcache[groupcounter].keys()):
  1055. if not groupcache[groupcounter][k]:
  1056. del groupcache[groupcounter][k]
  1057. groupcache[groupcounter]['args'] = args
  1058. groupcache[groupcounter]['body'] = []
  1059. groupcache[groupcounter]['externals'] = []
  1060. groupcache[groupcounter]['interfaced'] = []
  1061. groupcache[groupcounter]['vars'] = {}
  1062. groupcache[groupcounter]['entry'] = {}
  1063. # end of creation
  1064. if block == 'type':
  1065. groupcache[groupcounter]['varnames'] = []
  1066. if case in ['call', 'callfun']: # set parents variables
  1067. if name not in groupcache[groupcounter - 2]['externals']:
  1068. groupcache[groupcounter - 2]['externals'].append(name)
  1069. groupcache[groupcounter]['vars'] = copy.deepcopy(
  1070. groupcache[groupcounter - 2]['vars'])
  1071. try:
  1072. del groupcache[groupcounter]['vars'][name][
  1073. groupcache[groupcounter]['vars'][name]['attrspec'].index('external')]
  1074. except Exception:
  1075. pass
  1076. if block in ['function', 'subroutine']: # set global attributes
  1077. # name is fortran name
  1078. if bindcline:
  1079. bindcdat = re.search(crackline_bindlang, bindcline)
  1080. if bindcdat:
  1081. groupcache[groupcounter]['bindlang'] = {name: {}}
  1082. groupcache[groupcounter]['bindlang'][name]["lang"] = bindcdat.group('lang')
  1083. if bindcdat.group('lang_name'):
  1084. groupcache[groupcounter]['bindlang'][name]["name"] = bindcdat.group('lang_name')
  1085. try:
  1086. groupcache[groupcounter]['vars'][name] = appenddecl(
  1087. groupcache[groupcounter]['vars'][name], groupcache[groupcounter - 2]['vars'][''])
  1088. except Exception:
  1089. pass
  1090. if case == 'callfun': # return type
  1091. if result and result in groupcache[groupcounter]['vars']:
  1092. if not name == result:
  1093. groupcache[groupcounter]['vars'][name] = appenddecl(
  1094. groupcache[groupcounter]['vars'][name], groupcache[groupcounter]['vars'][result])
  1095. # if groupcounter>1: # name is interfaced
  1096. try:
  1097. groupcache[groupcounter - 2]['interfaced'].append(name)
  1098. except Exception:
  1099. pass
  1100. if block == 'function':
  1101. t = typespattern[0].match(m.group('before') + ' ' + name)
  1102. if t:
  1103. typespec, selector, attr, edecl = cracktypespec0(
  1104. t.group('this'), t.group('after'))
  1105. updatevars(typespec, selector, attr, edecl)
  1106. if case in ['call', 'callfun']:
  1107. grouplist[groupcounter - 1].append(groupcache[groupcounter])
  1108. grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
  1109. del grouplist[groupcounter]
  1110. groupcounter = groupcounter - 1 # end routine
  1111. grouplist[groupcounter - 1].append(groupcache[groupcounter])
  1112. grouplist[groupcounter - 1][-1]['body'] = grouplist[groupcounter]
  1113. del grouplist[groupcounter]
  1114. groupcounter = groupcounter - 1 # end interface
  1115. elif case == 'entry':
  1116. name, args, result, _ = _resolvenameargspattern(m.group('after'))
  1117. if name is not None:
  1118. if args:
  1119. args = rmbadname([x.strip()
  1120. for x in markoutercomma(args).split('@,@')])
  1121. else:
  1122. args = []
  1123. assert result is None, repr(result)
  1124. groupcache[groupcounter]['entry'][name] = args
  1125. previous_context = ('entry', name, groupcounter)
  1126. elif case == 'type':
  1127. typespec, selector, attr, edecl = cracktypespec0(
  1128. block, m.group('after'))
  1129. last_name = updatevars(typespec, selector, attr, edecl)
  1130. if last_name is not None:
  1131. previous_context = ('variable', last_name, groupcounter)
  1132. elif case in ['dimension', 'intent', 'optional', 'required', 'external', 'public', 'private', 'intrinsic']:
  1133. edecl = groupcache[groupcounter]['vars']
  1134. ll = m.group('after').strip()
  1135. i = ll.find('::')
  1136. if i < 0 and case == 'intent':
  1137. i = markouterparen(ll).find('@)@') - 2
  1138. ll = ll[:i + 1] + '::' + ll[i + 1:]
  1139. i = ll.find('::')
  1140. if ll[i:] == '::' and 'args' in groupcache[groupcounter]:
  1141. outmess('All arguments will have attribute %s%s\n' %
  1142. (m.group('this'), ll[:i]))
  1143. ll = ll + ','.join(groupcache[groupcounter]['args'])
  1144. if i < 0:
  1145. i = 0
  1146. pl = ''
  1147. else:
  1148. pl = ll[:i].strip()
  1149. ll = ll[i + 2:]
  1150. ch = markoutercomma(pl).split('@,@')
  1151. if len(ch) > 1:
  1152. pl = ch[0]
  1153. outmess('analyzeline: cannot handle multiple attributes without type specification. Ignoring %r.\n' % (
  1154. ','.join(ch[1:])))
  1155. last_name = None
  1156. for e in [x.strip() for x in markoutercomma(ll).split('@,@')]:
  1157. m1 = namepattern.match(e)
  1158. if not m1:
  1159. if case in ['public', 'private']:
  1160. k = ''
  1161. else:
  1162. print(m.groupdict())
  1163. outmess('analyzeline: no name pattern found in %s statement for %s. Skipping.\n' % (
  1164. case, repr(e)))
  1165. continue
  1166. else:
  1167. k = rmbadname1(m1.group('name'))
  1168. if case in ['public', 'private'] and k in {'operator', 'assignment'}:
  1169. k += m1.group('after')
  1170. if k not in edecl:
  1171. edecl[k] = {}
  1172. if case == 'dimension':
  1173. ap = case + m1.group('after')
  1174. if case == 'intent':
  1175. ap = m.group('this') + pl
  1176. if _intentcallbackpattern.match(ap):
  1177. if k not in groupcache[groupcounter]['args']:
  1178. if groupcounter > 1:
  1179. if '__user__' not in groupcache[groupcounter - 2]['name']:
  1180. outmess(
  1181. 'analyzeline: missing __user__ module (could be nothing)\n')
  1182. # fixes ticket 1693
  1183. if k != groupcache[groupcounter]['name']:
  1184. outmess('analyzeline: appending intent(callback) %s'
  1185. ' to %s arguments\n' % (k, groupcache[groupcounter]['name']))
  1186. groupcache[groupcounter]['args'].append(k)
  1187. else:
  1188. errmess(
  1189. f'analyzeline: intent(callback) {k} is ignored\n')
  1190. else:
  1191. errmess('analyzeline: intent(callback) %s is already'
  1192. ' in argument list\n' % (k))
  1193. if case in ['optional', 'required', 'public', 'external', 'private', 'intrinsic']:
  1194. ap = case
  1195. if 'attrspec' in edecl[k]:
  1196. edecl[k]['attrspec'].append(ap)
  1197. else:
  1198. edecl[k]['attrspec'] = [ap]
  1199. if case == 'external':
  1200. if groupcache[groupcounter]['block'] == 'program':
  1201. outmess('analyzeline: ignoring program arguments\n')
  1202. continue
  1203. if k not in groupcache[groupcounter]['args']:
  1204. continue
  1205. if 'externals' not in groupcache[groupcounter]:
  1206. groupcache[groupcounter]['externals'] = []
  1207. groupcache[groupcounter]['externals'].append(k)
  1208. last_name = k
  1209. groupcache[groupcounter]['vars'] = edecl
  1210. if last_name is not None:
  1211. previous_context = ('variable', last_name, groupcounter)
  1212. elif case == 'moduleprocedure':
  1213. groupcache[groupcounter]['implementedby'] = \
  1214. [x.strip() for x in m.group('after').split(',')]
  1215. elif case == 'parameter':
  1216. edecl = groupcache[groupcounter]['vars']
  1217. ll = m.group('after').strip()[1:-1]
  1218. last_name = None
  1219. for e in markoutercomma(ll).split('@,@'):
  1220. try:
  1221. k, initexpr = [x.strip() for x in e.split('=')]
  1222. except Exception:
  1223. outmess(
  1224. f'analyzeline: could not extract name,expr in parameter statement "{e}" of "{ll}\"\n')
  1225. continue
  1226. params = get_parameters(edecl)
  1227. k = rmbadname1(k)
  1228. if k not in edecl:
  1229. edecl[k] = {}
  1230. if '=' in edecl[k] and (not edecl[k]['='] == initexpr):
  1231. outmess('analyzeline: Overwriting the value of parameter "%s" ("%s") with "%s".\n' % (
  1232. k, edecl[k]['='], initexpr))
  1233. t = determineexprtype(initexpr, params)
  1234. if t:
  1235. if t.get('typespec') == 'real':
  1236. tt = list(initexpr)
  1237. for m in real16pattern.finditer(initexpr):
  1238. tt[m.start():m.end()] = list(
  1239. initexpr[m.start():m.end()].lower().replace('d', 'e'))
  1240. initexpr = ''.join(tt)
  1241. elif t.get('typespec') == 'complex':
  1242. initexpr = initexpr[1:].lower().replace('d', 'e').\
  1243. replace(',', '+1j*(')
  1244. try:
  1245. v = eval(initexpr, {}, params)
  1246. except (SyntaxError, NameError, TypeError) as msg:
  1247. errmess('analyzeline: Failed to evaluate %r. Ignoring: %s\n'
  1248. % (initexpr, msg))
  1249. continue
  1250. edecl[k]['='] = repr(v)
  1251. if 'attrspec' in edecl[k]:
  1252. edecl[k]['attrspec'].append('parameter')
  1253. else:
  1254. edecl[k]['attrspec'] = ['parameter']
  1255. last_name = k
  1256. groupcache[groupcounter]['vars'] = edecl
  1257. if last_name is not None:
  1258. previous_context = ('variable', last_name, groupcounter)
  1259. elif case == 'implicit':
  1260. if m.group('after').strip().lower() == 'none':
  1261. groupcache[groupcounter]['implicit'] = None
  1262. elif m.group('after'):
  1263. impl = groupcache[groupcounter].get('implicit', {})
  1264. if impl is None:
  1265. outmess(
  1266. 'analyzeline: Overwriting earlier "implicit none" statement.\n')
  1267. impl = {}
  1268. for e in markoutercomma(m.group('after')).split('@,@'):
  1269. decl = {}
  1270. m1 = re.match(
  1271. r'\s*(?P<this>.*?)\s*(\(\s*(?P<after>[a-z-, ]+)\s*\)\s*|)\Z', e, re.I)
  1272. if not m1:
  1273. outmess(
  1274. f'analyzeline: could not extract info of implicit statement part "{e}\"\n')
  1275. continue
  1276. m2 = typespattern4implicit.match(m1.group('this'))
  1277. if not m2:
  1278. outmess(
  1279. f'analyzeline: could not extract types pattern of implicit statement part "{e}\"\n')
  1280. continue
  1281. typespec, selector, attr, edecl = cracktypespec0(
  1282. m2.group('this'), m2.group('after'))
  1283. kindselect, charselect, typename = cracktypespec(
  1284. typespec, selector)
  1285. decl['typespec'] = typespec
  1286. decl['kindselector'] = kindselect
  1287. decl['charselector'] = charselect
  1288. decl['typename'] = typename
  1289. for k in list(decl.keys()):
  1290. if not decl[k]:
  1291. del decl[k]
  1292. for r in markoutercomma(m1.group('after')).split('@,@'):
  1293. if '-' in r:
  1294. try:
  1295. begc, endc = [x.strip() for x in r.split('-')]
  1296. except Exception:
  1297. outmess(
  1298. f'analyzeline: expected "<char>-<char>" instead of "{r}" in range list of implicit statement\n')
  1299. continue
  1300. else:
  1301. begc = endc = r.strip()
  1302. if not len(begc) == len(endc) == 1:
  1303. outmess(
  1304. f'analyzeline: expected "<char>-<char>" instead of "{r}" in range list of implicit statement (2)\n')
  1305. continue
  1306. for o in range(ord(begc), ord(endc) + 1):
  1307. impl[chr(o)] = decl
  1308. groupcache[groupcounter]['implicit'] = impl
  1309. elif case == 'data':
  1310. ll = []
  1311. dl = ''
  1312. il = ''
  1313. f = 0
  1314. fc = 1
  1315. inp = 0
  1316. for c in m.group('after'):
  1317. if not inp:
  1318. if c == "'":
  1319. fc = not fc
  1320. if c == '/' and fc:
  1321. f = f + 1
  1322. continue
  1323. if c == '(':
  1324. inp = inp + 1
  1325. elif c == ')':
  1326. inp = inp - 1
  1327. if f == 0:
  1328. dl = dl + c
  1329. elif f == 1:
  1330. il = il + c
  1331. elif f == 2:
  1332. dl = dl.strip()
  1333. if dl.startswith(','):
  1334. dl = dl[1:].strip()
  1335. ll.append([dl, il])
  1336. dl = c
  1337. il = ''
  1338. f = 0
  1339. if f == 2:
  1340. dl = dl.strip()
  1341. if dl.startswith(','):
  1342. dl = dl[1:].strip()
  1343. ll.append([dl, il])
  1344. vars = groupcache[groupcounter].get('vars', {})
  1345. last_name = None
  1346. for l in ll:
  1347. l[0], l[1] = l[0].strip().removeprefix(','), l[1].strip()
  1348. if l[0].startswith('('):
  1349. outmess(f'analyzeline: implied-DO list "{l[0]}" is not supported. Skipping.\n')
  1350. continue
  1351. for idx, v in enumerate(rmbadname([x.strip() for x in markoutercomma(l[0]).split('@,@')])):
  1352. if v.startswith('('):
  1353. outmess(f'analyzeline: implied-DO list "{v}" is not supported. Skipping.\n')
  1354. # XXX: subsequent init expressions may get wrong values.
  1355. # Ignoring since data statements are irrelevant for
  1356. # wrapping.
  1357. continue
  1358. if '!' in l[1]:
  1359. # Fixes gh-24746 pyf generation
  1360. # XXX: This essentially ignores the value for generating the pyf which is fine:
  1361. # integer dimension(3) :: mytab
  1362. # common /mycom/ mytab
  1363. # Since in any case it is initialized in the Fortran code
  1364. outmess(f'Comment line in declaration "{l[1]}" is not supported. Skipping.\n')
  1365. continue
  1366. vars.setdefault(v, {})
  1367. vtype = vars[v].get('typespec')
  1368. vdim = getdimension(vars[v])
  1369. matches = re.findall(r"\(.*?\)", l[1]) if vtype == 'complex' else l[1].split(',')
  1370. try:
  1371. new_val = f"(/{', '.join(matches)}/)" if vdim else matches[idx]
  1372. except IndexError:
  1373. # gh-24746
  1374. # Runs only if above code fails. Fixes the line
  1375. # DATA IVAR1, IVAR2, IVAR3, IVAR4, EVAR5 /4*0,0.0D0/
  1376. # by expanding to ['0', '0', '0', '0', '0.0d0']
  1377. if any("*" in m for m in matches):
  1378. expanded_list = []
  1379. for match in matches:
  1380. if "*" in match:
  1381. try:
  1382. multiplier, value = match.split("*")
  1383. expanded_list.extend([value.strip()] * int(multiplier))
  1384. except ValueError: # if int(multiplier) fails
  1385. expanded_list.append(match.strip())
  1386. else:
  1387. expanded_list.append(match.strip())
  1388. matches = expanded_list
  1389. new_val = f"(/{', '.join(matches)}/)" if vdim else matches[idx]
  1390. current_val = vars[v].get('=')
  1391. if current_val and (current_val != new_val):
  1392. outmess(f'analyzeline: changing init expression of "{v}" ("{current_val}") to "{new_val}\"\n')
  1393. vars[v]['='] = new_val
  1394. last_name = v
  1395. groupcache[groupcounter]['vars'] = vars
  1396. if last_name:
  1397. previous_context = ('variable', last_name, groupcounter)
  1398. elif case == 'common':
  1399. line = m.group('after').strip()
  1400. if not line[0] == '/':
  1401. line = '//' + line
  1402. cl = []
  1403. [_, bn, ol] = re.split('/', line, maxsplit=2) # noqa: RUF039
  1404. bn = bn.strip()
  1405. if not bn:
  1406. bn = '_BLNK_'
  1407. cl.append([bn, ol])
  1408. commonkey = {}
  1409. if 'common' in groupcache[groupcounter]:
  1410. commonkey = groupcache[groupcounter]['common']
  1411. for c in cl:
  1412. if c[0] not in commonkey:
  1413. commonkey[c[0]] = []
  1414. for i in [x.strip() for x in markoutercomma(c[1]).split('@,@')]:
  1415. if i:
  1416. commonkey[c[0]].append(i)
  1417. groupcache[groupcounter]['common'] = commonkey
  1418. previous_context = ('common', bn, groupcounter)
  1419. elif case == 'use':
  1420. m1 = re.match(
  1421. 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)
  1422. if m1:
  1423. mm = m1.groupdict()
  1424. if 'use' not in groupcache[groupcounter]:
  1425. groupcache[groupcounter]['use'] = {}
  1426. name = m1.group('name')
  1427. groupcache[groupcounter]['use'][name] = {}
  1428. isonly = 0
  1429. if 'list' in mm and mm['list'] is not None:
  1430. if 'notonly' in mm and mm['notonly'] is None:
  1431. isonly = 1
  1432. groupcache[groupcounter]['use'][name]['only'] = isonly
  1433. ll = [x.strip() for x in mm['list'].split(',')]
  1434. rl = {}
  1435. for l in ll:
  1436. if '=' in l:
  1437. m2 = re.match(
  1438. r'\A\s*(?P<local>\b\w+\b)\s*=\s*>\s*(?P<use>\b\w+\b)\s*\Z', l, re.I)
  1439. if m2:
  1440. rl[m2.group('local').strip()] = m2.group(
  1441. 'use').strip()
  1442. else:
  1443. outmess(
  1444. f'analyzeline: Not local=>use pattern found in {repr(l)}\n')
  1445. else:
  1446. rl[l] = l
  1447. groupcache[groupcounter]['use'][name]['map'] = rl
  1448. else:
  1449. print(m.groupdict())
  1450. outmess('analyzeline: Could not crack the use statement.\n')
  1451. elif case in ['f2pyenhancements']:
  1452. if 'f2pyenhancements' not in groupcache[groupcounter]:
  1453. groupcache[groupcounter]['f2pyenhancements'] = {}
  1454. d = groupcache[groupcounter]['f2pyenhancements']
  1455. if m.group('this') == 'usercode' and 'usercode' in d:
  1456. if isinstance(d['usercode'], str):
  1457. d['usercode'] = [d['usercode']]
  1458. d['usercode'].append(m.group('after'))
  1459. else:
  1460. d[m.group('this')] = m.group('after')
  1461. elif case == 'multiline':
  1462. if previous_context is None:
  1463. if verbose:
  1464. outmess('analyzeline: No context for multiline block.\n')
  1465. return
  1466. gc = groupcounter
  1467. appendmultiline(groupcache[gc],
  1468. previous_context[:2],
  1469. m.group('this'))
  1470. elif verbose > 1:
  1471. print(m.groupdict())
  1472. outmess('analyzeline: No code implemented for line.\n')
  1473. def appendmultiline(group, context_name, ml):
  1474. if 'f2pymultilines' not in group:
  1475. group['f2pymultilines'] = {}
  1476. d = group['f2pymultilines']
  1477. if context_name not in d:
  1478. d[context_name] = []
  1479. d[context_name].append(ml)
  1480. def cracktypespec0(typespec, ll):
  1481. selector = None
  1482. attr = None
  1483. if re.match(r'double\s*complex', typespec, re.I):
  1484. typespec = 'double complex'
  1485. elif re.match(r'double\s*precision', typespec, re.I):
  1486. typespec = 'double precision'
  1487. else:
  1488. typespec = typespec.strip().lower()
  1489. m1 = selectpattern.match(markouterparen(ll))
  1490. if not m1:
  1491. outmess(
  1492. 'cracktypespec0: no kind/char_selector pattern found for line.\n')
  1493. return
  1494. d = m1.groupdict()
  1495. for k in list(d.keys()):
  1496. d[k] = unmarkouterparen(d[k])
  1497. if typespec in ['complex', 'integer', 'logical', 'real', 'character', 'type']:
  1498. selector = d['this']
  1499. ll = d['after']
  1500. i = ll.find('::')
  1501. if i >= 0:
  1502. attr = ll[:i].strip()
  1503. ll = ll[i + 2:]
  1504. return typespec, selector, attr, ll
  1505. #####
  1506. namepattern = re.compile(r'\s*(?P<name>\b\w+\b)\s*(?P<after>.*)\s*\Z', re.I)
  1507. kindselector = re.compile(
  1508. r'\s*(\(\s*(kind\s*=)?\s*(?P<kind>.*)\s*\)|\*\s*(?P<kind2>.*?))\s*\Z', re.I)
  1509. charselector = re.compile(
  1510. r'\s*(\((?P<lenkind>.*)\)|\*\s*(?P<charlen>.*))\s*\Z', re.I)
  1511. lenkindpattern = re.compile(
  1512. r'\s*(kind\s*=\s*(?P<kind>.*?)\s*(@,@\s*len\s*=\s*(?P<len>.*)|)'
  1513. r'|(len\s*=\s*|)(?P<len2>.*?)\s*(@,@\s*(kind\s*=\s*|)(?P<kind2>.*)'
  1514. r'|(f2py_len\s*=\s*(?P<f2py_len>.*))|))\s*\Z', re.I)
  1515. lenarraypattern = re.compile(
  1516. 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)
  1517. def removespaces(expr):
  1518. expr = expr.strip()
  1519. if len(expr) <= 1:
  1520. return expr
  1521. expr2 = expr[0]
  1522. for i in range(1, len(expr) - 1):
  1523. if (expr[i] == ' ' and
  1524. ((expr[i + 1] in "()[]{}=+-/* ") or
  1525. (expr[i - 1] in "()[]{}=+-/* "))):
  1526. continue
  1527. expr2 = expr2 + expr[i]
  1528. expr2 = expr2 + expr[-1]
  1529. return expr2
  1530. def markinnerspaces(line):
  1531. """
  1532. The function replace all spaces in the input variable line which are
  1533. surrounded with quotation marks, with the triplet "@_@".
  1534. For instance, for the input "a 'b c'" the function returns "a 'b@_@c'"
  1535. Parameters
  1536. ----------
  1537. line : str
  1538. Returns
  1539. -------
  1540. str
  1541. """
  1542. fragment = ''
  1543. inside = False
  1544. current_quote = None
  1545. escaped = ''
  1546. for c in line:
  1547. if escaped == '\\' and c in ['\\', '\'', '"']:
  1548. fragment += c
  1549. escaped = c
  1550. continue
  1551. if not inside and c in ['\'', '"']:
  1552. current_quote = c
  1553. if c == current_quote:
  1554. inside = not inside
  1555. elif c == ' ' and inside:
  1556. fragment += '@_@'
  1557. continue
  1558. fragment += c
  1559. escaped = c # reset to non-backslash
  1560. return fragment
  1561. def updatevars(typespec, selector, attrspec, entitydecl):
  1562. """
  1563. Returns last_name, the variable name without special chars, parenthesis
  1564. or dimension specifiers.
  1565. Alters groupcache to add the name, typespec, attrspec (and possibly value)
  1566. of current variable.
  1567. """
  1568. global groupcache, groupcounter
  1569. last_name = None
  1570. kindselect, charselect, typename = cracktypespec(typespec, selector)
  1571. # Clean up outer commas, whitespace and undesired chars from attrspec
  1572. if attrspec:
  1573. attrspec = [x.strip() for x in markoutercomma(attrspec).split('@,@')]
  1574. l = []
  1575. c = re.compile(r'(?P<start>[a-zA-Z]+)')
  1576. for a in attrspec:
  1577. if not a:
  1578. continue
  1579. m = c.match(a)
  1580. if m:
  1581. s = m.group('start').lower()
  1582. a = s + a[len(s):]
  1583. l.append(a)
  1584. attrspec = l
  1585. el = [x.strip() for x in markoutercomma(entitydecl).split('@,@')]
  1586. el1 = []
  1587. for e in el:
  1588. for e1 in [x.strip() for x in markoutercomma(removespaces(markinnerspaces(e)), comma=' ').split('@ @')]:
  1589. if e1:
  1590. el1.append(e1.replace('@_@', ' '))
  1591. for e in el1:
  1592. m = namepattern.match(e)
  1593. if not m:
  1594. outmess(
  1595. f'updatevars: no name pattern found for entity={repr(e)}. Skipping.\n')
  1596. continue
  1597. ename = rmbadname1(m.group('name'))
  1598. edecl = {}
  1599. if ename in groupcache[groupcounter]['vars']:
  1600. edecl = groupcache[groupcounter]['vars'][ename].copy()
  1601. not_has_typespec = 'typespec' not in edecl
  1602. if not_has_typespec:
  1603. edecl['typespec'] = typespec
  1604. elif typespec and (not typespec == edecl['typespec']):
  1605. outmess('updatevars: attempt to change the type of "%s" ("%s") to "%s". Ignoring.\n' % (
  1606. ename, edecl['typespec'], typespec))
  1607. if 'kindselector' not in edecl:
  1608. edecl['kindselector'] = copy.copy(kindselect)
  1609. elif kindselect:
  1610. for k in list(kindselect.keys()):
  1611. if k in edecl['kindselector'] and (not kindselect[k] == edecl['kindselector'][k]):
  1612. outmess('updatevars: attempt to change the kindselector "%s" of "%s" ("%s") to "%s". Ignoring.\n' % (
  1613. k, ename, edecl['kindselector'][k], kindselect[k]))
  1614. else:
  1615. edecl['kindselector'][k] = copy.copy(kindselect[k])
  1616. if 'charselector' not in edecl and charselect:
  1617. if not_has_typespec:
  1618. edecl['charselector'] = charselect
  1619. else:
  1620. errmess('updatevars:%s: attempt to change empty charselector to %r. Ignoring.\n'
  1621. % (ename, charselect))
  1622. elif charselect:
  1623. for k in list(charselect.keys()):
  1624. if k in edecl['charselector'] and (not charselect[k] == edecl['charselector'][k]):
  1625. outmess('updatevars: attempt to change the charselector "%s" of "%s" ("%s") to "%s". Ignoring.\n' % (
  1626. k, ename, edecl['charselector'][k], charselect[k]))
  1627. else:
  1628. edecl['charselector'][k] = copy.copy(charselect[k])
  1629. if 'typename' not in edecl:
  1630. edecl['typename'] = typename
  1631. elif typename and (not edecl['typename'] == typename):
  1632. outmess('updatevars: attempt to change the typename of "%s" ("%s") to "%s". Ignoring.\n' % (
  1633. ename, edecl['typename'], typename))
  1634. if 'attrspec' not in edecl:
  1635. edecl['attrspec'] = copy.copy(attrspec)
  1636. elif attrspec:
  1637. for a in attrspec:
  1638. if a not in edecl['attrspec']:
  1639. edecl['attrspec'].append(a)
  1640. else:
  1641. edecl['typespec'] = copy.copy(typespec)
  1642. edecl['kindselector'] = copy.copy(kindselect)
  1643. edecl['charselector'] = copy.copy(charselect)
  1644. edecl['typename'] = typename
  1645. edecl['attrspec'] = copy.copy(attrspec)
  1646. if 'external' in (edecl.get('attrspec') or []) and e in groupcache[groupcounter]['args']:
  1647. if 'externals' not in groupcache[groupcounter]:
  1648. groupcache[groupcounter]['externals'] = []
  1649. groupcache[groupcounter]['externals'].append(e)
  1650. if m.group('after'):
  1651. m1 = lenarraypattern.match(markouterparen(m.group('after')))
  1652. if m1:
  1653. d1 = m1.groupdict()
  1654. for lk in ['len', 'array', 'init']:
  1655. if d1[lk + '2'] is not None:
  1656. d1[lk] = d1[lk + '2']
  1657. del d1[lk + '2']
  1658. for k in list(d1.keys()):
  1659. if d1[k] is not None:
  1660. d1[k] = unmarkouterparen(d1[k])
  1661. else:
  1662. del d1[k]
  1663. if 'len' in d1 and 'array' in d1:
  1664. if d1['len'] == '':
  1665. d1['len'] = d1['array']
  1666. del d1['array']
  1667. elif typespec == 'character':
  1668. if ('charselector' not in edecl) or (not edecl['charselector']):
  1669. edecl['charselector'] = {}
  1670. if 'len' in edecl['charselector']:
  1671. del edecl['charselector']['len']
  1672. edecl['charselector']['*'] = d1['len']
  1673. del d1['len']
  1674. else:
  1675. d1['array'] = d1['array'] + ',' + d1['len']
  1676. del d1['len']
  1677. errmess('updatevars: "%s %s" is mapped to "%s %s(%s)"\n' % (
  1678. typespec, e, typespec, ename, d1['array']))
  1679. if 'len' in d1:
  1680. if typespec in ['complex', 'integer', 'logical', 'real']:
  1681. if ('kindselector' not in edecl) or (not edecl['kindselector']):
  1682. edecl['kindselector'] = {}
  1683. edecl['kindselector']['*'] = d1['len']
  1684. del d1['len']
  1685. elif typespec == 'character':
  1686. if ('charselector' not in edecl) or (not edecl['charselector']):
  1687. edecl['charselector'] = {}
  1688. if 'len' in edecl['charselector']:
  1689. del edecl['charselector']['len']
  1690. edecl['charselector']['*'] = d1['len']
  1691. del d1['len']
  1692. if 'init' in d1:
  1693. if '=' in edecl and (not edecl['='] == d1['init']):
  1694. outmess('updatevars: attempt to change the init expression of "%s" ("%s") to "%s". Ignoring.\n' % (
  1695. ename, edecl['='], d1['init']))
  1696. else:
  1697. edecl['='] = d1['init']
  1698. if 'array' in d1:
  1699. dm = f"dimension({d1['array']})"
  1700. if 'attrspec' not in edecl or (not edecl['attrspec']):
  1701. edecl['attrspec'] = [dm]
  1702. else:
  1703. edecl['attrspec'].append(dm)
  1704. for dm1 in edecl['attrspec']:
  1705. if dm1[:9] == 'dimension' and dm1 != dm:
  1706. del edecl['attrspec'][-1]
  1707. errmess('updatevars:%s: attempt to change %r to %r. Ignoring.\n'
  1708. % (ename, dm1, dm))
  1709. break
  1710. else:
  1711. outmess('updatevars: could not crack entity declaration "%s". Ignoring.\n' % (
  1712. ename + m.group('after')))
  1713. for k in list(edecl.keys()):
  1714. if not edecl[k]:
  1715. del edecl[k]
  1716. groupcache[groupcounter]['vars'][ename] = edecl
  1717. if 'varnames' in groupcache[groupcounter]:
  1718. groupcache[groupcounter]['varnames'].append(ename)
  1719. last_name = ename
  1720. return last_name
  1721. def cracktypespec(typespec, selector):
  1722. kindselect = None
  1723. charselect = None
  1724. typename = None
  1725. if selector:
  1726. if typespec in ['complex', 'integer', 'logical', 'real']:
  1727. kindselect = kindselector.match(selector)
  1728. if not kindselect:
  1729. outmess(
  1730. f'cracktypespec: no kindselector pattern found for {repr(selector)}\n')
  1731. return
  1732. kindselect = kindselect.groupdict()
  1733. kindselect['*'] = kindselect['kind2']
  1734. del kindselect['kind2']
  1735. for k in list(kindselect.keys()):
  1736. if not kindselect[k]:
  1737. del kindselect[k]
  1738. for k, i in list(kindselect.items()):
  1739. kindselect[k] = rmbadname1(i)
  1740. elif typespec == 'character':
  1741. charselect = charselector.match(selector)
  1742. if not charselect:
  1743. outmess(
  1744. f'cracktypespec: no charselector pattern found for {repr(selector)}\n')
  1745. return
  1746. charselect = charselect.groupdict()
  1747. charselect['*'] = charselect['charlen']
  1748. del charselect['charlen']
  1749. if charselect['lenkind']:
  1750. lenkind = lenkindpattern.match(
  1751. markoutercomma(charselect['lenkind']))
  1752. lenkind = lenkind.groupdict()
  1753. for lk in ['len', 'kind']:
  1754. if lenkind[lk + '2']:
  1755. lenkind[lk] = lenkind[lk + '2']
  1756. charselect[lk] = lenkind[lk]
  1757. del lenkind[lk + '2']
  1758. if lenkind['f2py_len'] is not None:
  1759. # used to specify the length of assumed length strings
  1760. charselect['f2py_len'] = lenkind['f2py_len']
  1761. del charselect['lenkind']
  1762. for k in list(charselect.keys()):
  1763. if not charselect[k]:
  1764. del charselect[k]
  1765. for k, i in list(charselect.items()):
  1766. charselect[k] = rmbadname1(i)
  1767. elif typespec == 'type':
  1768. typename = re.match(r'\s*\(\s*(?P<name>\w+)\s*\)', selector, re.I)
  1769. if typename:
  1770. typename = typename.group('name')
  1771. else:
  1772. outmess('cracktypespec: no typename found in %s\n' %
  1773. (repr(typespec + selector)))
  1774. else:
  1775. outmess(f'cracktypespec: no selector used for {repr(selector)}\n')
  1776. return kindselect, charselect, typename
  1777. ######
  1778. def setattrspec(decl, attr, force=0):
  1779. if not decl:
  1780. decl = {}
  1781. if not attr:
  1782. return decl
  1783. if 'attrspec' not in decl:
  1784. decl['attrspec'] = [attr]
  1785. return decl
  1786. if force:
  1787. decl['attrspec'].append(attr)
  1788. if attr in decl['attrspec']:
  1789. return decl
  1790. if attr == 'static' and 'automatic' not in decl['attrspec']:
  1791. decl['attrspec'].append(attr)
  1792. elif attr == 'automatic' and 'static' not in decl['attrspec']:
  1793. decl['attrspec'].append(attr)
  1794. elif attr == 'public':
  1795. if 'private' not in decl['attrspec']:
  1796. decl['attrspec'].append(attr)
  1797. elif attr == 'private':
  1798. if 'public' not in decl['attrspec']:
  1799. decl['attrspec'].append(attr)
  1800. else:
  1801. decl['attrspec'].append(attr)
  1802. return decl
  1803. def setkindselector(decl, sel, force=0):
  1804. if not decl:
  1805. decl = {}
  1806. if not sel:
  1807. return decl
  1808. if 'kindselector' not in decl:
  1809. decl['kindselector'] = sel
  1810. return decl
  1811. for k in list(sel.keys()):
  1812. if force or k not in decl['kindselector']:
  1813. decl['kindselector'][k] = sel[k]
  1814. return decl
  1815. def setcharselector(decl, sel, force=0):
  1816. if not decl:
  1817. decl = {}
  1818. if not sel:
  1819. return decl
  1820. if 'charselector' not in decl:
  1821. decl['charselector'] = sel
  1822. return decl
  1823. for k in list(sel.keys()):
  1824. if force or k not in decl['charselector']:
  1825. decl['charselector'][k] = sel[k]
  1826. return decl
  1827. def getblockname(block, unknown='unknown'):
  1828. if 'name' in block:
  1829. return block['name']
  1830. return unknown
  1831. # post processing
  1832. def setmesstext(block):
  1833. global filepositiontext
  1834. try:
  1835. filepositiontext = f"In: {block['from']}:{block['name']}\n"
  1836. except Exception:
  1837. pass
  1838. def get_usedict(block):
  1839. usedict = {}
  1840. if 'parent_block' in block:
  1841. usedict = get_usedict(block['parent_block'])
  1842. if 'use' in block:
  1843. usedict.update(block['use'])
  1844. return usedict
  1845. def get_useparameters(block, param_map=None):
  1846. global f90modulevars
  1847. if param_map is None:
  1848. param_map = {}
  1849. usedict = get_usedict(block)
  1850. if not usedict:
  1851. return param_map
  1852. for usename, mapping in list(usedict.items()):
  1853. usename = usename.lower()
  1854. if usename not in f90modulevars:
  1855. outmess('get_useparameters: no module %s info used by %s\n' %
  1856. (usename, block.get('name')))
  1857. continue
  1858. mvars = f90modulevars[usename]
  1859. params = get_parameters(mvars)
  1860. if not params:
  1861. continue
  1862. # XXX: apply mapping
  1863. if mapping:
  1864. errmess(f'get_useparameters: mapping for {mapping} not impl.\n')
  1865. for k, v in list(params.items()):
  1866. if k in param_map:
  1867. outmess('get_useparameters: overriding parameter %s with'
  1868. ' value from module %s\n' % (repr(k), repr(usename)))
  1869. param_map[k] = v
  1870. return param_map
  1871. def postcrack2(block, tab='', param_map=None):
  1872. global f90modulevars
  1873. if not f90modulevars:
  1874. return block
  1875. if isinstance(block, list):
  1876. ret = [postcrack2(g, tab=tab + '\t', param_map=param_map)
  1877. for g in block]
  1878. return ret
  1879. setmesstext(block)
  1880. outmess(f"{tab}Block: {block['name']}\n", 0)
  1881. if param_map is None:
  1882. param_map = get_useparameters(block)
  1883. if param_map is not None and 'vars' in block:
  1884. vars = block['vars']
  1885. for n in list(vars.keys()):
  1886. var = vars[n]
  1887. if 'kindselector' in var:
  1888. kind = var['kindselector']
  1889. if 'kind' in kind:
  1890. val = kind['kind']
  1891. if val in param_map:
  1892. kind['kind'] = param_map[val]
  1893. new_body = [postcrack2(b, tab=tab + '\t', param_map=param_map)
  1894. for b in block['body']]
  1895. block['body'] = new_body
  1896. return block
  1897. def postcrack(block, args=None, tab=''):
  1898. """
  1899. TODO:
  1900. function return values
  1901. determine expression types if in argument list
  1902. """
  1903. global usermodules, onlyfunctions
  1904. if isinstance(block, list):
  1905. gret = []
  1906. uret = []
  1907. for g in block:
  1908. setmesstext(g)
  1909. g = postcrack(g, tab=tab + '\t')
  1910. # sort user routines to appear first
  1911. if 'name' in g and '__user__' in g['name']:
  1912. uret.append(g)
  1913. else:
  1914. gret.append(g)
  1915. return uret + gret
  1916. setmesstext(block)
  1917. if not isinstance(block, dict) and 'block' not in block:
  1918. raise Exception('postcrack: Expected block dictionary instead of ' +
  1919. str(block))
  1920. if 'name' in block and not block['name'] == 'unknown_interface':
  1921. outmess(f"{tab}Block: {block['name']}\n", 0)
  1922. block = analyzeargs(block)
  1923. block = analyzecommon(block)
  1924. block['vars'] = analyzevars(block)
  1925. block['sortvars'] = sortvarnames(block['vars'])
  1926. if block.get('args'):
  1927. args = block['args']
  1928. block['body'] = analyzebody(block, args, tab=tab)
  1929. userisdefined = []
  1930. if 'use' in block:
  1931. useblock = block['use']
  1932. for k in list(useblock.keys()):
  1933. if '__user__' in k:
  1934. userisdefined.append(k)
  1935. else:
  1936. useblock = {}
  1937. name = ''
  1938. if 'name' in block:
  1939. name = block['name']
  1940. # and not userisdefined: # Build a __user__ module
  1941. if block.get('externals'):
  1942. interfaced = []
  1943. if 'interfaced' in block:
  1944. interfaced = block['interfaced']
  1945. mvars = copy.copy(block['vars'])
  1946. if name:
  1947. mname = name + '__user__routines'
  1948. else:
  1949. mname = 'unknown__user__routines'
  1950. if mname in userisdefined:
  1951. i = 1
  1952. while f"{mname}_{i}" in userisdefined:
  1953. i = i + 1
  1954. mname = f"{mname}_{i}"
  1955. interface = {'block': 'interface', 'body': [],
  1956. 'vars': {}, 'name': name + '_user_interface'}
  1957. for e in block['externals']:
  1958. if e in interfaced:
  1959. edef = []
  1960. j = -1
  1961. for b in block['body']:
  1962. j = j + 1
  1963. if b['block'] == 'interface':
  1964. i = -1
  1965. for bb in b['body']:
  1966. i = i + 1
  1967. if 'name' in bb and bb['name'] == e:
  1968. edef = copy.copy(bb)
  1969. del b['body'][i]
  1970. break
  1971. if edef:
  1972. if not b['body']:
  1973. del block['body'][j]
  1974. del interfaced[interfaced.index(e)]
  1975. break
  1976. interface['body'].append(edef)
  1977. elif e in mvars and not isexternal(mvars[e]):
  1978. interface['vars'][e] = mvars[e]
  1979. if interface['vars'] or interface['body']:
  1980. block['interfaced'] = interfaced
  1981. mblock = {'block': 'python module', 'body': [
  1982. interface], 'vars': {}, 'name': mname, 'interfaced': block['externals']}
  1983. useblock[mname] = {}
  1984. usermodules.append(mblock)
  1985. if useblock:
  1986. block['use'] = useblock
  1987. return block
  1988. def sortvarnames(vars):
  1989. indep = []
  1990. dep = []
  1991. for v in list(vars.keys()):
  1992. if 'depend' in vars[v] and vars[v]['depend']:
  1993. dep.append(v)
  1994. else:
  1995. indep.append(v)
  1996. n = len(dep)
  1997. i = 0
  1998. while dep: # XXX: How to catch dependence cycles correctly?
  1999. v = dep[0]
  2000. fl = 0
  2001. for w in dep[1:]:
  2002. if w in vars[v]['depend']:
  2003. fl = 1
  2004. break
  2005. if fl:
  2006. dep = dep[1:] + [v]
  2007. i = i + 1
  2008. if i > n:
  2009. errmess('sortvarnames: failed to compute dependencies because'
  2010. ' of cyclic dependencies between '
  2011. + ', '.join(dep) + '\n')
  2012. indep = indep + dep
  2013. break
  2014. else:
  2015. indep.append(v)
  2016. dep = dep[1:]
  2017. n = len(dep)
  2018. i = 0
  2019. return indep
  2020. def analyzecommon(block):
  2021. if not hascommon(block):
  2022. return block
  2023. commonvars = []
  2024. for k in list(block['common'].keys()):
  2025. comvars = []
  2026. for e in block['common'][k]:
  2027. m = re.match(
  2028. r'\A\s*\b(?P<name>.*?)\b\s*(\((?P<dims>.*?)\)|)\s*\Z', e, re.I)
  2029. if m:
  2030. dims = []
  2031. if m.group('dims'):
  2032. dims = [x.strip()
  2033. for x in markoutercomma(m.group('dims')).split('@,@')]
  2034. n = rmbadname1(m.group('name').strip())
  2035. if n in block['vars']:
  2036. if 'attrspec' in block['vars'][n]:
  2037. block['vars'][n]['attrspec'].append(
  2038. f"dimension({','.join(dims)})")
  2039. else:
  2040. block['vars'][n]['attrspec'] = [
  2041. f"dimension({','.join(dims)})"]
  2042. elif dims:
  2043. block['vars'][n] = {
  2044. 'attrspec': [f"dimension({','.join(dims)})"]}
  2045. else:
  2046. block['vars'][n] = {}
  2047. if n not in commonvars:
  2048. commonvars.append(n)
  2049. else:
  2050. n = e
  2051. errmess(
  2052. f'analyzecommon: failed to extract "<name>[(<dims>)]" from "{e}" in common /{k}/.\n')
  2053. comvars.append(n)
  2054. block['common'][k] = comvars
  2055. if 'commonvars' not in block:
  2056. block['commonvars'] = commonvars
  2057. else:
  2058. block['commonvars'] = block['commonvars'] + commonvars
  2059. return block
  2060. def analyzebody(block, args, tab=''):
  2061. global usermodules, skipfuncs, onlyfuncs, f90modulevars
  2062. setmesstext(block)
  2063. maybe_private = {
  2064. key: value
  2065. for key, value in block['vars'].items()
  2066. if 'attrspec' not in value or 'public' not in value['attrspec']
  2067. }
  2068. body = []
  2069. for b in block['body']:
  2070. b['parent_block'] = block
  2071. if b['block'] in ['function', 'subroutine']:
  2072. if args is not None and b['name'] not in args:
  2073. continue
  2074. else:
  2075. as_ = b['args']
  2076. # Add private members to skipfuncs for gh-23879
  2077. if b['name'] in maybe_private.keys():
  2078. skipfuncs.append(b['name'])
  2079. if b['name'] in skipfuncs:
  2080. continue
  2081. if onlyfuncs and b['name'] not in onlyfuncs:
  2082. continue
  2083. b['saved_interface'] = crack2fortrangen(
  2084. b, '\n' + ' ' * 6, as_interface=True)
  2085. else:
  2086. as_ = args
  2087. b = postcrack(b, as_, tab=tab + '\t')
  2088. if b['block'] in ['interface', 'abstract interface'] and \
  2089. not b['body'] and not b.get('implementedby'):
  2090. if 'f2pyenhancements' not in b:
  2091. continue
  2092. if b['block'].replace(' ', '') == 'pythonmodule':
  2093. usermodules.append(b)
  2094. else:
  2095. if b['block'] == 'module':
  2096. f90modulevars[b['name']] = b['vars']
  2097. body.append(b)
  2098. return body
  2099. def buildimplicitrules(block):
  2100. setmesstext(block)
  2101. implicitrules = defaultimplicitrules
  2102. attrrules = {}
  2103. if 'implicit' in block:
  2104. if block['implicit'] is None:
  2105. implicitrules = None
  2106. if verbose > 1:
  2107. outmess(
  2108. f"buildimplicitrules: no implicit rules for routine {repr(block['name'])}.\n")
  2109. else:
  2110. for k in list(block['implicit'].keys()):
  2111. if block['implicit'][k].get('typespec') not in ['static', 'automatic']:
  2112. implicitrules[k] = block['implicit'][k]
  2113. else:
  2114. attrrules[k] = block['implicit'][k]['typespec']
  2115. return implicitrules, attrrules
  2116. def myeval(e, g=None, l=None):
  2117. """ Like `eval` but returns only integers and floats """
  2118. r = eval(e, g, l)
  2119. if type(r) in [int, float]:
  2120. return r
  2121. raise ValueError(f'r={r!r}')
  2122. getlincoef_re_1 = re.compile(r'\A\b\w+\b\Z', re.I)
  2123. def getlincoef(e, xset): # e = a*x+b ; x in xset
  2124. """
  2125. Obtain ``a`` and ``b`` when ``e == "a*x+b"``, where ``x`` is a symbol in
  2126. xset.
  2127. >>> getlincoef('2*x + 1', {'x'})
  2128. (2, 1, 'x')
  2129. >>> getlincoef('3*x + x*2 + 2 + 1', {'x'})
  2130. (5, 3, 'x')
  2131. >>> getlincoef('0', {'x'})
  2132. (0, 0, None)
  2133. >>> getlincoef('0*x', {'x'})
  2134. (0, 0, 'x')
  2135. >>> getlincoef('x*x', {'x'})
  2136. (None, None, None)
  2137. This can be tricked by sufficiently complex expressions
  2138. >>> getlincoef('(x - 0.5)*(x - 1.5)*(x - 1)*x + 2*x + 3', {'x'})
  2139. (2.0, 3.0, 'x')
  2140. """
  2141. try:
  2142. c = int(myeval(e, {}, {}))
  2143. return 0, c, None
  2144. except Exception:
  2145. pass
  2146. if getlincoef_re_1.match(e):
  2147. return 1, 0, e
  2148. len_e = len(e)
  2149. for x in xset:
  2150. if len(x) > len_e:
  2151. continue
  2152. if re.search(r'\w\s*\([^)]*\b' + x + r'\b', e):
  2153. # skip function calls having x as an argument, e.g max(1, x)
  2154. continue
  2155. re_1 = re.compile(r'(?P<before>.*?)\b' + x + r'\b(?P<after>.*)', re.I)
  2156. m = re_1.match(e)
  2157. if m:
  2158. try:
  2159. m1 = re_1.match(e)
  2160. while m1:
  2161. ee = f"{m1.group('before')}({0}){m1.group('after')}"
  2162. m1 = re_1.match(ee)
  2163. b = myeval(ee, {}, {})
  2164. m1 = re_1.match(e)
  2165. while m1:
  2166. ee = f"{m1.group('before')}({1}){m1.group('after')}"
  2167. m1 = re_1.match(ee)
  2168. a = myeval(ee, {}, {}) - b
  2169. m1 = re_1.match(e)
  2170. while m1:
  2171. ee = f"{m1.group('before')}({0.5}){m1.group('after')}"
  2172. m1 = re_1.match(ee)
  2173. c = myeval(ee, {}, {})
  2174. # computing another point to be sure that expression is linear
  2175. m1 = re_1.match(e)
  2176. while m1:
  2177. ee = f"{m1.group('before')}({1.5}){m1.group('after')}"
  2178. m1 = re_1.match(ee)
  2179. c2 = myeval(ee, {}, {})
  2180. if (a * 0.5 + b == c and a * 1.5 + b == c2):
  2181. return a, b, x
  2182. except Exception:
  2183. pass
  2184. break
  2185. return None, None, None
  2186. word_pattern = re.compile(r'\b[a-z][\w$]*\b', re.I)
  2187. def _get_depend_dict(name, vars, deps):
  2188. if name in vars:
  2189. words = vars[name].get('depend', [])
  2190. if '=' in vars[name] and not isstring(vars[name]):
  2191. for word in word_pattern.findall(vars[name]['=']):
  2192. # The word_pattern may return values that are not
  2193. # only variables, they can be string content for instance
  2194. if word not in words and word in vars and word != name:
  2195. words.append(word)
  2196. for word in words[:]:
  2197. for w in deps.get(word, []) \
  2198. or _get_depend_dict(word, vars, deps):
  2199. if w not in words:
  2200. words.append(w)
  2201. else:
  2202. outmess(f'_get_depend_dict: no dependence info for {repr(name)}\n')
  2203. words = []
  2204. deps[name] = words
  2205. return words
  2206. def _calc_depend_dict(vars):
  2207. names = list(vars.keys())
  2208. depend_dict = {}
  2209. for n in names:
  2210. _get_depend_dict(n, vars, depend_dict)
  2211. return depend_dict
  2212. def get_sorted_names(vars):
  2213. depend_dict = _calc_depend_dict(vars)
  2214. names = []
  2215. for name in list(depend_dict.keys()):
  2216. if not depend_dict[name]:
  2217. names.append(name)
  2218. del depend_dict[name]
  2219. while depend_dict:
  2220. for name, lst in list(depend_dict.items()):
  2221. new_lst = [n for n in lst if n in depend_dict]
  2222. if not new_lst:
  2223. names.append(name)
  2224. del depend_dict[name]
  2225. else:
  2226. depend_dict[name] = new_lst
  2227. return [name for name in names if name in vars]
  2228. def _kind_func(string):
  2229. # XXX: return something sensible.
  2230. if string[0] in "'\"":
  2231. string = string[1:-1]
  2232. if real16pattern.match(string):
  2233. return 8
  2234. elif real8pattern.match(string):
  2235. return 4
  2236. return 'kind(' + string + ')'
  2237. def _selected_int_kind_func(r):
  2238. # XXX: This should be processor dependent
  2239. m = 10 ** r
  2240. if m <= 2 ** 8:
  2241. return 1
  2242. if m <= 2 ** 16:
  2243. return 2
  2244. if m <= 2 ** 32:
  2245. return 4
  2246. if m <= 2 ** 63:
  2247. return 8
  2248. if m <= 2 ** 128:
  2249. return 16
  2250. return -1
  2251. def _selected_real_kind_func(p, r=0, radix=0):
  2252. # XXX: This should be processor dependent
  2253. # This is only verified for 0 <= p <= 20, possibly good for p <= 33 and above
  2254. if p < 7:
  2255. return 4
  2256. if p < 16:
  2257. return 8
  2258. machine = platform.machine().lower()
  2259. if machine.startswith(('aarch64', 'alpha', 'arm64', 'loongarch', 'mips', 'power', 'ppc', 'riscv', 's390x', 'sparc')):
  2260. if p <= 33:
  2261. return 16
  2262. elif p < 19:
  2263. return 10
  2264. elif p <= 33:
  2265. return 16
  2266. return -1
  2267. def get_parameters(vars, global_params={}):
  2268. params = copy.copy(global_params)
  2269. g_params = copy.copy(global_params)
  2270. for name, func in [('kind', _kind_func),
  2271. ('selected_int_kind', _selected_int_kind_func),
  2272. ('selected_real_kind', _selected_real_kind_func), ]:
  2273. if name not in g_params:
  2274. g_params[name] = func
  2275. param_names = []
  2276. for n in get_sorted_names(vars):
  2277. if 'attrspec' in vars[n] and 'parameter' in vars[n]['attrspec']:
  2278. param_names.append(n)
  2279. kind_re = re.compile(r'\bkind\s*\(\s*(?P<value>.*)\s*\)', re.I)
  2280. selected_int_kind_re = re.compile(
  2281. r'\bselected_int_kind\s*\(\s*(?P<value>.*)\s*\)', re.I)
  2282. selected_kind_re = re.compile(
  2283. r'\bselected_(int|real)_kind\s*\(\s*(?P<value>.*)\s*\)', re.I)
  2284. for n in param_names:
  2285. if '=' in vars[n]:
  2286. v = vars[n]['=']
  2287. if islogical(vars[n]):
  2288. v = v.lower()
  2289. for repl in [
  2290. ('.false.', 'False'),
  2291. ('.true.', 'True'),
  2292. # TODO: test .eq., .neq., etc replacements.
  2293. ]:
  2294. v = v.replace(*repl)
  2295. v = kind_re.sub(r'kind("\1")', v)
  2296. v = selected_int_kind_re.sub(r'selected_int_kind(\1)', v)
  2297. # We need to act according to the data.
  2298. # The easy case is if the data has a kind-specifier,
  2299. # then we may easily remove those specifiers.
  2300. # However, it may be that the user uses other specifiers...(!)
  2301. is_replaced = False
  2302. if 'kindselector' in vars[n]:
  2303. # Remove kind specifier (including those defined
  2304. # by parameters)
  2305. if 'kind' in vars[n]['kindselector']:
  2306. orig_v_len = len(v)
  2307. v = v.replace('_' + vars[n]['kindselector']['kind'], '')
  2308. # Again, this will be true if even a single specifier
  2309. # has been replaced, see comment above.
  2310. is_replaced = len(v) < orig_v_len
  2311. if not is_replaced:
  2312. if not selected_kind_re.match(v):
  2313. v_ = v.split('_')
  2314. # In case there are additive parameters
  2315. if len(v_) > 1:
  2316. v = ''.join(v_[:-1]).lower().replace(v_[-1].lower(), '')
  2317. # Currently this will not work for complex numbers.
  2318. # There is missing code for extracting a complex number,
  2319. # which may be defined in either of these:
  2320. # a) (Re, Im)
  2321. # b) cmplx(Re, Im)
  2322. # c) dcmplx(Re, Im)
  2323. # d) cmplx(Re, Im, <prec>)
  2324. if isdouble(vars[n]):
  2325. tt = list(v)
  2326. for m in real16pattern.finditer(v):
  2327. tt[m.start():m.end()] = list(
  2328. v[m.start():m.end()].lower().replace('d', 'e'))
  2329. v = ''.join(tt)
  2330. elif iscomplex(vars[n]):
  2331. outmess(f'get_parameters[TODO]: '
  2332. f'implement evaluation of complex expression {v}\n')
  2333. dimspec = ([s.removeprefix('dimension').strip()
  2334. for s in vars[n]['attrspec']
  2335. if s.startswith('dimension')] or [None])[0]
  2336. # Handle _dp for gh-6624
  2337. # Also fixes gh-20460
  2338. if real16pattern.search(v):
  2339. v = 8
  2340. elif real8pattern.search(v):
  2341. v = 4
  2342. try:
  2343. params[n] = param_eval(v, g_params, params, dimspec=dimspec)
  2344. except Exception as msg:
  2345. params[n] = v
  2346. outmess(f'get_parameters: got "{msg}" on {n!r}\n')
  2347. if isstring(vars[n]) and isinstance(params[n], int):
  2348. params[n] = chr(params[n])
  2349. nl = n.lower()
  2350. if nl != n:
  2351. params[nl] = params[n]
  2352. else:
  2353. print(vars[n])
  2354. outmess(f'get_parameters:parameter {n!r} does not have value?!\n')
  2355. return params
  2356. def _eval_length(length, params):
  2357. if length in ['(:)', '(*)', '*']:
  2358. return '(*)'
  2359. return _eval_scalar(length, params)
  2360. _is_kind_number = re.compile(r'\d+_').match
  2361. def _eval_scalar(value, params):
  2362. if _is_kind_number(value):
  2363. value = value.split('_')[0]
  2364. try:
  2365. # TODO: use symbolic from PR #19805
  2366. value = eval(value, {}, params)
  2367. value = (repr if isinstance(value, str) else str)(value)
  2368. except (NameError, SyntaxError, TypeError):
  2369. return value
  2370. except Exception as msg:
  2371. errmess('"%s" in evaluating %r '
  2372. '(available names: %s)\n'
  2373. % (msg, value, list(params.keys())))
  2374. return value
  2375. def analyzevars(block):
  2376. """
  2377. Sets correct dimension information for each variable/parameter
  2378. """
  2379. global f90modulevars
  2380. setmesstext(block)
  2381. implicitrules, attrrules = buildimplicitrules(block)
  2382. vars = copy.copy(block['vars'])
  2383. if block['block'] == 'function' and block['name'] not in vars:
  2384. vars[block['name']] = {}
  2385. if '' in block['vars']:
  2386. del vars['']
  2387. if 'attrspec' in block['vars']['']:
  2388. gen = block['vars']['']['attrspec']
  2389. for n in set(vars) | {b['name'] for b in block['body']}:
  2390. for k in ['public', 'private']:
  2391. if k in gen:
  2392. vars[n] = setattrspec(vars.get(n, {}), k)
  2393. svars = []
  2394. args = block['args']
  2395. for a in args:
  2396. try:
  2397. vars[a]
  2398. svars.append(a)
  2399. except KeyError:
  2400. pass
  2401. for n in list(vars.keys()):
  2402. if n not in args:
  2403. svars.append(n)
  2404. params = get_parameters(vars, get_useparameters(block))
  2405. # At this point, params are read and interpreted, but
  2406. # the params used to define vars are not yet parsed
  2407. dep_matches = {}
  2408. name_match = re.compile(r'[A-Za-z][\w$]*').match
  2409. for v in list(vars.keys()):
  2410. m = name_match(v)
  2411. if m:
  2412. n = v[m.start():m.end()]
  2413. try:
  2414. dep_matches[n]
  2415. except KeyError:
  2416. dep_matches[n] = re.compile(r'.*\b%s\b' % (v), re.I).match
  2417. for n in svars:
  2418. if n[0] in list(attrrules.keys()):
  2419. vars[n] = setattrspec(vars[n], attrrules[n[0]])
  2420. if 'typespec' not in vars[n]:
  2421. if not ('attrspec' in vars[n] and 'external' in vars[n]['attrspec']):
  2422. if implicitrules:
  2423. ln0 = n[0].lower()
  2424. for k in list(implicitrules[ln0].keys()):
  2425. if k == 'typespec' and implicitrules[ln0][k] == 'undefined':
  2426. continue
  2427. if k not in vars[n]:
  2428. vars[n][k] = implicitrules[ln0][k]
  2429. elif k == 'attrspec':
  2430. for l in implicitrules[ln0][k]:
  2431. vars[n] = setattrspec(vars[n], l)
  2432. elif n in block['args']:
  2433. outmess('analyzevars: typespec of variable %s is not defined in routine %s.\n' % (
  2434. repr(n), block['name']))
  2435. if 'charselector' in vars[n]:
  2436. if 'len' in vars[n]['charselector']:
  2437. l = vars[n]['charselector']['len']
  2438. try:
  2439. l = str(eval(l, {}, params))
  2440. except Exception:
  2441. pass
  2442. vars[n]['charselector']['len'] = l
  2443. if 'kindselector' in vars[n]:
  2444. if 'kind' in vars[n]['kindselector']:
  2445. l = vars[n]['kindselector']['kind']
  2446. try:
  2447. l = str(eval(l, {}, params))
  2448. except Exception:
  2449. pass
  2450. vars[n]['kindselector']['kind'] = l
  2451. dimension_exprs = {}
  2452. if 'attrspec' in vars[n]:
  2453. attr = vars[n]['attrspec']
  2454. attr.reverse()
  2455. vars[n]['attrspec'] = []
  2456. dim, intent, depend, check, note = None, None, None, None, None
  2457. for a in attr:
  2458. if a[:9] == 'dimension':
  2459. dim = (a[9:].strip())[1:-1]
  2460. elif a[:6] == 'intent':
  2461. intent = (a[6:].strip())[1:-1]
  2462. elif a[:6] == 'depend':
  2463. depend = (a[6:].strip())[1:-1]
  2464. elif a[:5] == 'check':
  2465. check = (a[5:].strip())[1:-1]
  2466. elif a[:4] == 'note':
  2467. note = (a[4:].strip())[1:-1]
  2468. else:
  2469. vars[n] = setattrspec(vars[n], a)
  2470. if intent:
  2471. if 'intent' not in vars[n]:
  2472. vars[n]['intent'] = []
  2473. for c in [x.strip() for x in markoutercomma(intent).split('@,@')]:
  2474. # Remove spaces so that 'in out' becomes 'inout'
  2475. tmp = c.replace(' ', '')
  2476. if tmp not in vars[n]['intent']:
  2477. vars[n]['intent'].append(tmp)
  2478. intent = None
  2479. if note:
  2480. note = note.replace('\\n\\n', '\n\n')
  2481. note = note.replace('\\n ', '\n')
  2482. if 'note' not in vars[n]:
  2483. vars[n]['note'] = [note]
  2484. else:
  2485. vars[n]['note'].append(note)
  2486. note = None
  2487. if depend is not None:
  2488. if 'depend' not in vars[n]:
  2489. vars[n]['depend'] = []
  2490. for c in rmbadname([x.strip() for x in markoutercomma(depend).split('@,@')]):
  2491. if c not in vars[n]['depend']:
  2492. vars[n]['depend'].append(c)
  2493. depend = None
  2494. if check is not None:
  2495. if 'check' not in vars[n]:
  2496. vars[n]['check'] = []
  2497. for c in [x.strip() for x in markoutercomma(check).split('@,@')]:
  2498. if c not in vars[n]['check']:
  2499. vars[n]['check'].append(c)
  2500. check = None
  2501. if dim and 'dimension' not in vars[n]:
  2502. vars[n]['dimension'] = []
  2503. for d in rmbadname(
  2504. [x.strip() for x in markoutercomma(dim).split('@,@')]
  2505. ):
  2506. # d is the expression inside the dimension declaration
  2507. # Evaluate `d` with respect to params
  2508. try:
  2509. # the dimension for this variable depends on a
  2510. # previously defined parameter
  2511. d = param_parse(d, params)
  2512. except (ValueError, IndexError, KeyError):
  2513. outmess(
  2514. 'analyzevars: could not parse dimension for '
  2515. f'variable {d!r}\n'
  2516. )
  2517. dim_char = ':' if d == ':' else '*'
  2518. if d == dim_char:
  2519. dl = [dim_char]
  2520. else:
  2521. dl = markoutercomma(d, ':').split('@:@')
  2522. if len(dl) == 2 and '*' in dl: # e.g. dimension(5:*)
  2523. dl = ['*']
  2524. d = '*'
  2525. if len(dl) == 1 and dl[0] != dim_char:
  2526. dl = ['1', dl[0]]
  2527. if len(dl) == 2:
  2528. d1, d2 = map(symbolic.Expr.parse, dl)
  2529. dsize = d2 - d1 + 1
  2530. d = dsize.tostring(language=symbolic.Language.C)
  2531. # find variables v that define d as a linear
  2532. # function, `d == a * v + b`, and store
  2533. # coefficients a and b for further analysis.
  2534. solver_and_deps = {}
  2535. for v in block['vars']:
  2536. s = symbolic.as_symbol(v)
  2537. if dsize.contains(s):
  2538. try:
  2539. a, b = dsize.linear_solve(s)
  2540. def solve_v(s, a=a, b=b):
  2541. return (s - b) / a
  2542. all_symbols = set(a.symbols())
  2543. all_symbols.update(b.symbols())
  2544. except RuntimeError as msg:
  2545. # d is not a linear function of v,
  2546. # however, if v can be determined
  2547. # from d using other means,
  2548. # implement the corresponding
  2549. # solve_v function here.
  2550. solve_v = None
  2551. all_symbols = set(dsize.symbols())
  2552. v_deps = {
  2553. s.data for s in all_symbols
  2554. if s.data in vars}
  2555. solver_and_deps[v] = solve_v, list(v_deps)
  2556. # Note that dsize may contain symbols that are
  2557. # not defined in block['vars']. Here we assume
  2558. # these correspond to Fortran/C intrinsic
  2559. # functions or that are defined by other
  2560. # means. We'll let the compiler validate the
  2561. # definiteness of such symbols.
  2562. dimension_exprs[d] = solver_and_deps
  2563. vars[n]['dimension'].append(d)
  2564. if 'check' not in vars[n] and 'args' in block and n in block['args']:
  2565. # n is an argument that has no checks defined. Here we
  2566. # generate some consistency checks for n, and when n is an
  2567. # array, generate checks for its dimensions and construct
  2568. # initialization expressions.
  2569. n_deps = vars[n].get('depend', [])
  2570. n_checks = []
  2571. n_is_input = l_or(isintent_in, isintent_inout,
  2572. isintent_inplace)(vars[n])
  2573. if isarray(vars[n]): # n is array
  2574. for i, d in enumerate(vars[n]['dimension']):
  2575. coeffs_and_deps = dimension_exprs.get(d)
  2576. if coeffs_and_deps is None:
  2577. # d is `:` or `*` or a constant expression
  2578. pass
  2579. elif n_is_input:
  2580. # n is an input array argument and its shape
  2581. # may define variables used in dimension
  2582. # specifications.
  2583. for v, (solver, deps) in coeffs_and_deps.items():
  2584. def compute_deps(v, deps):
  2585. for v1 in coeffs_and_deps.get(v, [None, []])[1]:
  2586. if v1 not in deps:
  2587. deps.add(v1)
  2588. compute_deps(v1, deps)
  2589. all_deps = set()
  2590. compute_deps(v, all_deps)
  2591. if (v in n_deps
  2592. or '=' in vars[v]
  2593. or 'depend' in vars[v]):
  2594. # Skip a variable that
  2595. # - n depends on
  2596. # - has user-defined initialization expression
  2597. # - has user-defined dependencies
  2598. continue
  2599. if solver is not None and v not in all_deps:
  2600. # v can be solved from d, hence, we
  2601. # make it an optional argument with
  2602. # initialization expression:
  2603. is_required = False
  2604. init = solver(symbolic.as_symbol(
  2605. f'shape({n}, {i})'))
  2606. init = init.tostring(
  2607. language=symbolic.Language.C)
  2608. vars[v]['='] = init
  2609. # n needs to be initialized before v. So,
  2610. # making v dependent on n and on any
  2611. # variables in solver or d.
  2612. vars[v]['depend'] = [n] + deps
  2613. if 'check' not in vars[v]:
  2614. # add check only when no
  2615. # user-specified checks exist
  2616. vars[v]['check'] = [
  2617. f'shape({n}, {i}) == {d}']
  2618. else:
  2619. # d is a non-linear function on v,
  2620. # hence, v must be a required input
  2621. # argument that n will depend on
  2622. is_required = True
  2623. if 'intent' not in vars[v]:
  2624. vars[v]['intent'] = []
  2625. if 'in' not in vars[v]['intent']:
  2626. vars[v]['intent'].append('in')
  2627. # v needs to be initialized before n
  2628. n_deps.append(v)
  2629. n_checks.append(
  2630. f'shape({n}, {i}) == {d}')
  2631. v_attr = vars[v].get('attrspec', [])
  2632. if not ('optional' in v_attr
  2633. or 'required' in v_attr):
  2634. v_attr.append(
  2635. 'required' if is_required else 'optional')
  2636. if v_attr:
  2637. vars[v]['attrspec'] = v_attr
  2638. if coeffs_and_deps is not None:
  2639. # extend v dependencies with ones specified in attrspec
  2640. for v, (solver, deps) in coeffs_and_deps.items():
  2641. v_deps = vars[v].get('depend', [])
  2642. for aa in vars[v].get('attrspec', []):
  2643. if aa.startswith('depend'):
  2644. aa = ''.join(aa.split())
  2645. v_deps.extend(aa[7:-1].split(','))
  2646. if v_deps:
  2647. vars[v]['depend'] = list(set(v_deps))
  2648. if n not in v_deps:
  2649. n_deps.append(v)
  2650. elif isstring(vars[n]):
  2651. if 'charselector' in vars[n]:
  2652. if '*' in vars[n]['charselector']:
  2653. length = _eval_length(vars[n]['charselector']['*'],
  2654. params)
  2655. vars[n]['charselector']['*'] = length
  2656. elif 'len' in vars[n]['charselector']:
  2657. length = _eval_length(vars[n]['charselector']['len'],
  2658. params)
  2659. del vars[n]['charselector']['len']
  2660. vars[n]['charselector']['*'] = length
  2661. if n_checks:
  2662. vars[n]['check'] = n_checks
  2663. if n_deps:
  2664. vars[n]['depend'] = list(set(n_deps))
  2665. if '=' in vars[n]:
  2666. if 'attrspec' not in vars[n]:
  2667. vars[n]['attrspec'] = []
  2668. if ('optional' not in vars[n]['attrspec']) and \
  2669. ('required' not in vars[n]['attrspec']):
  2670. vars[n]['attrspec'].append('optional')
  2671. if 'depend' not in vars[n]:
  2672. vars[n]['depend'] = []
  2673. for v, m in list(dep_matches.items()):
  2674. if m(vars[n]['=']):
  2675. vars[n]['depend'].append(v)
  2676. if not vars[n]['depend']:
  2677. del vars[n]['depend']
  2678. if isscalar(vars[n]):
  2679. vars[n]['='] = _eval_scalar(vars[n]['='], params)
  2680. for n in list(vars.keys()):
  2681. if n == block['name']: # n is block name
  2682. if 'note' in vars[n]:
  2683. block['note'] = vars[n]['note']
  2684. if block['block'] == 'function':
  2685. if 'result' in block and block['result'] in vars:
  2686. vars[n] = appenddecl(vars[n], vars[block['result']])
  2687. if 'prefix' in block:
  2688. pr = block['prefix']
  2689. pr1 = pr.replace('pure', '')
  2690. ispure = (not pr == pr1)
  2691. pr = pr1.replace('recursive', '')
  2692. isrec = (not pr == pr1)
  2693. m = typespattern[0].match(pr)
  2694. if m:
  2695. typespec, selector, attr, edecl = cracktypespec0(
  2696. m.group('this'), m.group('after'))
  2697. kindselect, charselect, typename = cracktypespec(
  2698. typespec, selector)
  2699. vars[n]['typespec'] = typespec
  2700. try:
  2701. if block['result']:
  2702. vars[block['result']]['typespec'] = typespec
  2703. except Exception:
  2704. pass
  2705. if kindselect:
  2706. if 'kind' in kindselect:
  2707. try:
  2708. kindselect['kind'] = eval(
  2709. kindselect['kind'], {}, params)
  2710. except Exception:
  2711. pass
  2712. vars[n]['kindselector'] = kindselect
  2713. if charselect:
  2714. vars[n]['charselector'] = charselect
  2715. if typename:
  2716. vars[n]['typename'] = typename
  2717. if ispure:
  2718. vars[n] = setattrspec(vars[n], 'pure')
  2719. if isrec:
  2720. vars[n] = setattrspec(vars[n], 'recursive')
  2721. else:
  2722. outmess(
  2723. f"analyzevars: prefix ({repr(block['prefix'])}) were not used\n")
  2724. if block['block'] not in ['module', 'pythonmodule', 'python module', 'block data']:
  2725. if 'commonvars' in block:
  2726. neededvars = copy.copy(block['args'] + block['commonvars'])
  2727. else:
  2728. neededvars = copy.copy(block['args'])
  2729. for n in list(vars.keys()):
  2730. if l_or(isintent_callback, isintent_aux)(vars[n]):
  2731. neededvars.append(n)
  2732. if 'entry' in block:
  2733. neededvars.extend(list(block['entry'].keys()))
  2734. for k in list(block['entry'].keys()):
  2735. for n in block['entry'][k]:
  2736. if n not in neededvars:
  2737. neededvars.append(n)
  2738. if block['block'] == 'function':
  2739. if 'result' in block:
  2740. neededvars.append(block['result'])
  2741. else:
  2742. neededvars.append(block['name'])
  2743. if block['block'] in ['subroutine', 'function']:
  2744. name = block['name']
  2745. if name in vars and 'intent' in vars[name]:
  2746. block['intent'] = vars[name]['intent']
  2747. if block['block'] == 'type':
  2748. neededvars.extend(list(vars.keys()))
  2749. for n in list(vars.keys()):
  2750. if n not in neededvars:
  2751. del vars[n]
  2752. return vars
  2753. analyzeargs_re_1 = re.compile(r'\A[a-z]+[\w$]*\Z', re.I)
  2754. def param_eval(v, g_params, params, dimspec=None):
  2755. """
  2756. Creates a dictionary of indices and values for each parameter in a
  2757. parameter array to be evaluated later.
  2758. WARNING: It is not possible to initialize multidimensional array
  2759. parameters e.g. dimension(-3:1, 4, 3:5) at this point. This is because in
  2760. Fortran initialization through array constructor requires the RESHAPE
  2761. intrinsic function. Since the right-hand side of the parameter declaration
  2762. is not executed in f2py, but rather at the compiled c/fortran extension,
  2763. later, it is not possible to execute a reshape of a parameter array.
  2764. One issue remains: if the user wants to access the array parameter from
  2765. python, we should either
  2766. 1) allow them to access the parameter array using python standard indexing
  2767. (which is often incompatible with the original fortran indexing)
  2768. 2) allow the parameter array to be accessed in python as a dictionary with
  2769. fortran indices as keys
  2770. We are choosing 2 for now.
  2771. """
  2772. if dimspec is None:
  2773. try:
  2774. p = eval(v, g_params, params)
  2775. except Exception as msg:
  2776. p = v
  2777. outmess(f'param_eval: got "{msg}" on {v!r}\n')
  2778. return p
  2779. # This is an array parameter.
  2780. # First, we parse the dimension information
  2781. if len(dimspec) < 2 or dimspec[::len(dimspec) - 1] != "()":
  2782. raise ValueError(f'param_eval: dimension {dimspec} can\'t be parsed')
  2783. dimrange = dimspec[1:-1].split(',')
  2784. if len(dimrange) == 1:
  2785. # e.g. dimension(2) or dimension(-1:1)
  2786. dimrange = dimrange[0].split(':')
  2787. # now, dimrange is a list of 1 or 2 elements
  2788. if len(dimrange) == 1:
  2789. bound = param_parse(dimrange[0], params)
  2790. dimrange = range(1, int(bound) + 1)
  2791. else:
  2792. lbound = param_parse(dimrange[0], params)
  2793. ubound = param_parse(dimrange[1], params)
  2794. dimrange = range(int(lbound), int(ubound) + 1)
  2795. else:
  2796. raise ValueError('param_eval: multidimensional array parameters '
  2797. f'{dimspec} not supported')
  2798. # Parse parameter value
  2799. v = (v[2:-2] if v.startswith('(/') else v).split(',')
  2800. v_eval = []
  2801. for item in v:
  2802. try:
  2803. item = eval(item, g_params, params)
  2804. except Exception as msg:
  2805. outmess(f'param_eval: got "{msg}" on {item!r}\n')
  2806. v_eval.append(item)
  2807. p = dict(zip(dimrange, v_eval))
  2808. return p
  2809. def param_parse(d, params):
  2810. """Recursively parse array dimensions.
  2811. Parses the declaration of an array variable or parameter
  2812. `dimension` keyword, and is called recursively if the
  2813. dimension for this array is a previously defined parameter
  2814. (found in `params`).
  2815. Parameters
  2816. ----------
  2817. d : str
  2818. Fortran expression describing the dimension of an array.
  2819. params : dict
  2820. Previously parsed parameters declared in the Fortran source file.
  2821. Returns
  2822. -------
  2823. out : str
  2824. Parsed dimension expression.
  2825. Examples
  2826. --------
  2827. * If the line being analyzed is
  2828. `integer, parameter, dimension(2) :: pa = (/ 3, 5 /)`
  2829. then `d = 2` and we return immediately, with
  2830. >>> d = '2'
  2831. >>> param_parse(d, params)
  2832. 2
  2833. * If the line being analyzed is
  2834. `integer, parameter, dimension(pa) :: pb = (/1, 2, 3/)`
  2835. then `d = 'pa'`; since `pa` is a previously parsed parameter,
  2836. and `pa = 3`, we call `param_parse` recursively, to obtain
  2837. >>> d = 'pa'
  2838. >>> params = {'pa': 3}
  2839. >>> param_parse(d, params)
  2840. 3
  2841. * If the line being analyzed is
  2842. `integer, parameter, dimension(pa(1)) :: pb = (/1, 2, 3/)`
  2843. then `d = 'pa(1)'`; since `pa` is a previously parsed parameter,
  2844. and `pa(1) = 3`, we call `param_parse` recursively, to obtain
  2845. >>> d = 'pa(1)'
  2846. >>> params = dict(pa={1: 3, 2: 5})
  2847. >>> param_parse(d, params)
  2848. 3
  2849. """
  2850. if "(" in d:
  2851. # this dimension expression is an array
  2852. dname = d[:d.find("(")]
  2853. ddims = d[d.find("(") + 1:d.rfind(")")]
  2854. # this dimension expression is also a parameter;
  2855. # parse it recursively
  2856. index = int(param_parse(ddims, params))
  2857. return str(params[dname][index])
  2858. elif d in params:
  2859. return str(params[d])
  2860. else:
  2861. for p in params:
  2862. re_1 = re.compile(
  2863. r'(?P<before>.*?)\b' + p + r'\b(?P<after>.*)', re.I
  2864. )
  2865. m = re_1.match(d)
  2866. while m:
  2867. d = m.group('before') + \
  2868. str(params[p]) + m.group('after')
  2869. m = re_1.match(d)
  2870. return d
  2871. def expr2name(a, block, args=[]):
  2872. orig_a = a
  2873. a_is_expr = not analyzeargs_re_1.match(a)
  2874. if a_is_expr: # `a` is an expression
  2875. implicitrules, attrrules = buildimplicitrules(block)
  2876. at = determineexprtype(a, block['vars'], implicitrules)
  2877. na = 'e_'
  2878. for c in a:
  2879. c = c.lower()
  2880. if c not in string.ascii_lowercase + string.digits:
  2881. c = '_'
  2882. na = na + c
  2883. if na[-1] == '_':
  2884. na = na + 'e'
  2885. else:
  2886. na = na + '_e'
  2887. a = na
  2888. while a in block['vars'] or a in block['args']:
  2889. a = a + 'r'
  2890. if a in args:
  2891. k = 1
  2892. while a + str(k) in args:
  2893. k = k + 1
  2894. a = a + str(k)
  2895. if a_is_expr:
  2896. block['vars'][a] = at
  2897. else:
  2898. if a not in block['vars']:
  2899. block['vars'][a] = block['vars'].get(orig_a, {})
  2900. if 'externals' in block and orig_a in block['externals'] + block['interfaced']:
  2901. block['vars'][a] = setattrspec(block['vars'][a], 'external')
  2902. return a
  2903. def analyzeargs(block):
  2904. setmesstext(block)
  2905. implicitrules, _ = buildimplicitrules(block)
  2906. if 'args' not in block:
  2907. block['args'] = []
  2908. args = []
  2909. for a in block['args']:
  2910. a = expr2name(a, block, args)
  2911. args.append(a)
  2912. block['args'] = args
  2913. if 'entry' in block:
  2914. for k, args1 in list(block['entry'].items()):
  2915. for a in args1:
  2916. if a not in block['vars']:
  2917. block['vars'][a] = {}
  2918. for b in block['body']:
  2919. if b['name'] in args:
  2920. if 'externals' not in block:
  2921. block['externals'] = []
  2922. if b['name'] not in block['externals']:
  2923. block['externals'].append(b['name'])
  2924. if 'result' in block and block['result'] not in block['vars']:
  2925. block['vars'][block['result']] = {}
  2926. return block
  2927. determineexprtype_re_1 = re.compile(r'\A\(.+?,.+?\)\Z', re.I)
  2928. determineexprtype_re_2 = re.compile(r'\A[+-]?\d+(_(?P<name>\w+)|)\Z', re.I)
  2929. determineexprtype_re_3 = re.compile(
  2930. r'\A[+-]?[\d.]+[-\d+de.]*(_(?P<name>\w+)|)\Z', re.I)
  2931. determineexprtype_re_4 = re.compile(r'\A\(.*\)\Z', re.I)
  2932. determineexprtype_re_5 = re.compile(r'\A(?P<name>\w+)\s*\(.*?\)\s*\Z', re.I)
  2933. def _ensure_exprdict(r):
  2934. if isinstance(r, int):
  2935. return {'typespec': 'integer'}
  2936. if isinstance(r, float):
  2937. return {'typespec': 'real'}
  2938. if isinstance(r, complex):
  2939. return {'typespec': 'complex'}
  2940. if isinstance(r, dict):
  2941. return r
  2942. raise AssertionError(repr(r))
  2943. def determineexprtype(expr, vars, rules={}):
  2944. if expr in vars:
  2945. return _ensure_exprdict(vars[expr])
  2946. expr = expr.strip()
  2947. if determineexprtype_re_1.match(expr):
  2948. return {'typespec': 'complex'}
  2949. m = determineexprtype_re_2.match(expr)
  2950. if m:
  2951. if 'name' in m.groupdict() and m.group('name'):
  2952. outmess(
  2953. f'determineexprtype: selected kind types not supported ({repr(expr)})\n')
  2954. return {'typespec': 'integer'}
  2955. m = determineexprtype_re_3.match(expr)
  2956. if m:
  2957. if 'name' in m.groupdict() and m.group('name'):
  2958. outmess(
  2959. f'determineexprtype: selected kind types not supported ({repr(expr)})\n')
  2960. return {'typespec': 'real'}
  2961. for op in ['+', '-', '*', '/']:
  2962. for e in [x.strip() for x in markoutercomma(expr, comma=op).split('@' + op + '@')]:
  2963. if e in vars:
  2964. return _ensure_exprdict(vars[e])
  2965. t = {}
  2966. if determineexprtype_re_4.match(expr): # in parenthesis
  2967. t = determineexprtype(expr[1:-1], vars, rules)
  2968. else:
  2969. m = determineexprtype_re_5.match(expr)
  2970. if m:
  2971. rn = m.group('name')
  2972. t = determineexprtype(m.group('name'), vars, rules)
  2973. if t and 'attrspec' in t:
  2974. del t['attrspec']
  2975. if not t:
  2976. if rn[0] in rules:
  2977. return _ensure_exprdict(rules[rn[0]])
  2978. if expr[0] in '\'"':
  2979. return {'typespec': 'character', 'charselector': {'*': '*'}}
  2980. if not t:
  2981. outmess(
  2982. f'determineexprtype: could not determine expressions ({repr(expr)}) type.\n')
  2983. return t
  2984. ######
  2985. def crack2fortrangen(block, tab='\n', as_interface=False):
  2986. global skipfuncs, onlyfuncs
  2987. setmesstext(block)
  2988. ret = ''
  2989. if isinstance(block, list):
  2990. for g in block:
  2991. if g and g['block'] in ['function', 'subroutine']:
  2992. if g['name'] in skipfuncs:
  2993. continue
  2994. if onlyfuncs and g['name'] not in onlyfuncs:
  2995. continue
  2996. ret = ret + crack2fortrangen(g, tab, as_interface=as_interface)
  2997. return ret
  2998. prefix = ''
  2999. name = ''
  3000. args = ''
  3001. blocktype = block['block']
  3002. if blocktype == 'program':
  3003. return ''
  3004. argsl = []
  3005. if 'name' in block:
  3006. name = block['name']
  3007. if 'args' in block:
  3008. vars = block['vars']
  3009. for a in block['args']:
  3010. a = expr2name(a, block, argsl)
  3011. if not isintent_callback(vars[a]):
  3012. argsl.append(a)
  3013. if block['block'] == 'function' or argsl:
  3014. args = f"({','.join(argsl)})"
  3015. f2pyenhancements = ''
  3016. if 'f2pyenhancements' in block:
  3017. for k in list(block['f2pyenhancements'].keys()):
  3018. f2pyenhancements = '%s%s%s %s' % (
  3019. f2pyenhancements, tab + tabchar, k, block['f2pyenhancements'][k])
  3020. intent_lst = block.get('intent', [])[:]
  3021. if blocktype == 'function' and 'callback' in intent_lst:
  3022. intent_lst.remove('callback')
  3023. if intent_lst:
  3024. f2pyenhancements = '%s%sintent(%s) %s' %\
  3025. (f2pyenhancements, tab + tabchar,
  3026. ','.join(intent_lst), name)
  3027. use = ''
  3028. if 'use' in block:
  3029. use = use2fortran(block['use'], tab + tabchar)
  3030. common = ''
  3031. if 'common' in block:
  3032. common = common2fortran(block['common'], tab + tabchar)
  3033. if name == 'unknown_interface':
  3034. name = ''
  3035. result = ''
  3036. if 'result' in block:
  3037. result = f" result ({block['result']})"
  3038. if block['result'] not in argsl:
  3039. argsl.append(block['result'])
  3040. body = crack2fortrangen(block['body'], tab + tabchar, as_interface=as_interface)
  3041. vars = vars2fortran(
  3042. block, block['vars'], argsl, tab + tabchar, as_interface=as_interface)
  3043. mess = ''
  3044. if 'from' in block and not as_interface:
  3045. mess = f"! in {block['from']}"
  3046. if 'entry' in block:
  3047. entry_stmts = ''
  3048. for k, i in list(block['entry'].items()):
  3049. entry_stmts = f"{entry_stmts}{tab + tabchar}entry {k}({','.join(i)})"
  3050. body = body + entry_stmts
  3051. if blocktype == 'block data' and name == '_BLOCK_DATA_':
  3052. name = ''
  3053. ret = '%s%s%s %s%s%s %s%s%s%s%s%s%send %s %s' % (
  3054. tab, prefix, blocktype, name, args, result, mess, f2pyenhancements, use, vars, common, body, tab, blocktype, name)
  3055. return ret
  3056. def common2fortran(common, tab=''):
  3057. ret = ''
  3058. for k in list(common.keys()):
  3059. if k == '_BLNK_':
  3060. ret = f"{ret}{tab}common {','.join(common[k])}"
  3061. else:
  3062. ret = f"{ret}{tab}common /{k}/ {','.join(common[k])}"
  3063. return ret
  3064. def use2fortran(use, tab=''):
  3065. ret = ''
  3066. for m in list(use.keys()):
  3067. ret = f'{ret}{tab}use {m},'
  3068. if use[m] == {}:
  3069. if ret and ret[-1] == ',':
  3070. ret = ret[:-1]
  3071. continue
  3072. if 'only' in use[m] and use[m]['only']:
  3073. ret = f'{ret} only:'
  3074. if 'map' in use[m] and use[m]['map']:
  3075. c = ' '
  3076. for k in list(use[m]['map'].keys()):
  3077. if k == use[m]['map'][k]:
  3078. ret = f'{ret}{c}{k}'
  3079. c = ','
  3080. else:
  3081. ret = f"{ret}{c}{k}=>{use[m]['map'][k]}"
  3082. c = ','
  3083. if ret and ret[-1] == ',':
  3084. ret = ret[:-1]
  3085. return ret
  3086. def true_intent_list(var):
  3087. lst = var['intent']
  3088. ret = []
  3089. for intent in lst:
  3090. try:
  3091. f = globals()[f'isintent_{intent}']
  3092. except KeyError:
  3093. pass
  3094. else:
  3095. if f(var):
  3096. ret.append(intent)
  3097. return ret
  3098. def vars2fortran(block, vars, args, tab='', as_interface=False):
  3099. setmesstext(block)
  3100. ret = ''
  3101. nout = []
  3102. for a in args:
  3103. if a in block['vars']:
  3104. nout.append(a)
  3105. if 'commonvars' in block:
  3106. for a in block['commonvars']:
  3107. if a in vars:
  3108. if a not in nout:
  3109. nout.append(a)
  3110. else:
  3111. errmess(
  3112. f'vars2fortran: Confused?!: "{a}" is not defined in vars.\n')
  3113. if 'varnames' in block:
  3114. nout.extend(block['varnames'])
  3115. if not as_interface:
  3116. for a in list(vars.keys()):
  3117. if a not in nout:
  3118. nout.append(a)
  3119. for a in nout:
  3120. if 'depend' in vars[a]:
  3121. for d in vars[a]['depend']:
  3122. if d in vars and 'depend' in vars[d] and a in vars[d]['depend']:
  3123. errmess(
  3124. f'vars2fortran: Warning: cross-dependence between variables "{a}" and "{d}\"\n')
  3125. if 'externals' in block and a in block['externals']:
  3126. if isintent_callback(vars[a]):
  3127. ret = f'{ret}{tab}intent(callback) {a}'
  3128. ret = f'{ret}{tab}external {a}'
  3129. if isoptional(vars[a]):
  3130. ret = f'{ret}{tab}optional {a}'
  3131. if a in vars and 'typespec' not in vars[a]:
  3132. continue
  3133. cont = 1
  3134. for b in block['body']:
  3135. if a == b['name'] and b['block'] == 'function':
  3136. cont = 0
  3137. break
  3138. if cont:
  3139. continue
  3140. if a not in vars:
  3141. show(vars)
  3142. outmess(f'vars2fortran: No definition for argument "{a}".\n')
  3143. continue
  3144. if a == block['name']:
  3145. if block['block'] != 'function' or block.get('result'):
  3146. # 1) skip declaring a variable that name matches with
  3147. # subroutine name
  3148. # 2) skip declaring function when its type is
  3149. # declared via `result` construction
  3150. continue
  3151. if 'typespec' not in vars[a]:
  3152. if 'attrspec' in vars[a] and 'external' in vars[a]['attrspec']:
  3153. if a in args:
  3154. ret = f'{ret}{tab}external {a}'
  3155. continue
  3156. show(vars[a])
  3157. outmess(f'vars2fortran: No typespec for argument "{a}".\n')
  3158. continue
  3159. vardef = vars[a]['typespec']
  3160. if vardef == 'type' and 'typename' in vars[a]:
  3161. vardef = f"{vardef}({vars[a]['typename']})"
  3162. selector = {}
  3163. if 'kindselector' in vars[a]:
  3164. selector = vars[a]['kindselector']
  3165. elif 'charselector' in vars[a]:
  3166. selector = vars[a]['charselector']
  3167. if '*' in selector:
  3168. if selector['*'] in ['*', ':']:
  3169. vardef = f"{vardef}*({selector['*']})"
  3170. else:
  3171. vardef = f"{vardef}*{selector['*']}"
  3172. elif 'len' in selector:
  3173. vardef = f"{vardef}(len={selector['len']}"
  3174. if 'kind' in selector:
  3175. vardef = f"{vardef},kind={selector['kind']})"
  3176. else:
  3177. vardef = f'{vardef})'
  3178. elif 'kind' in selector:
  3179. vardef = f"{vardef}(kind={selector['kind']})"
  3180. c = ' '
  3181. if 'attrspec' in vars[a]:
  3182. attr = [l for l in vars[a]['attrspec']
  3183. if l not in ['external']]
  3184. if as_interface and 'intent(in)' in attr and 'intent(out)' in attr:
  3185. # In Fortran, intent(in, out) are conflicting while
  3186. # intent(in, out) can be specified only via
  3187. # `!f2py intent(out) ..`.
  3188. # So, for the Fortran interface, we'll drop
  3189. # intent(out) to resolve the conflict.
  3190. attr.remove('intent(out)')
  3191. if attr:
  3192. vardef = f"{vardef}, {','.join(attr)}"
  3193. c = ','
  3194. if 'dimension' in vars[a]:
  3195. vardef = f"{vardef}{c}dimension({','.join(vars[a]['dimension'])})"
  3196. c = ','
  3197. if 'intent' in vars[a]:
  3198. lst = true_intent_list(vars[a])
  3199. if lst:
  3200. vardef = f"{vardef}{c}intent({','.join(lst)})"
  3201. c = ','
  3202. if 'check' in vars[a]:
  3203. vardef = f"{vardef}{c}check({','.join(vars[a]['check'])})"
  3204. c = ','
  3205. if 'depend' in vars[a]:
  3206. vardef = f"{vardef}{c}depend({','.join(vars[a]['depend'])})"
  3207. c = ','
  3208. if '=' in vars[a]:
  3209. v = vars[a]['=']
  3210. if vars[a]['typespec'] in ['complex', 'double complex']:
  3211. try:
  3212. v = eval(v)
  3213. v = f'({v.real},{v.imag})'
  3214. except Exception:
  3215. pass
  3216. vardef = f'{vardef} :: {a}={v}'
  3217. else:
  3218. vardef = f'{vardef} :: {a}'
  3219. ret = f'{ret}{tab}{vardef}'
  3220. return ret
  3221. ######
  3222. # We expose post_processing_hooks as global variable so that
  3223. # user-libraries could register their own hooks to f2py.
  3224. post_processing_hooks = []
  3225. def crackfortran(files):
  3226. global usermodules, post_processing_hooks
  3227. outmess('Reading fortran codes...\n', 0)
  3228. readfortrancode(files, crackline)
  3229. outmess('Post-processing...\n', 0)
  3230. usermodules = []
  3231. postlist = postcrack(grouplist[0])
  3232. outmess('Applying post-processing hooks...\n', 0)
  3233. for hook in post_processing_hooks:
  3234. outmess(f' {hook.__name__}\n', 0)
  3235. postlist = traverse(postlist, hook)
  3236. outmess('Post-processing (stage 2)...\n', 0)
  3237. postlist = postcrack2(postlist)
  3238. return usermodules + postlist
  3239. def crack2fortran(block):
  3240. global f2py_version
  3241. pyf = crack2fortrangen(block) + '\n'
  3242. header = """! -*- f90 -*-
  3243. ! Note: the context of this file is case sensitive.
  3244. """
  3245. footer = """
  3246. ! This file was auto-generated with f2py (version:%s).
  3247. ! See:
  3248. ! https://web.archive.org/web/20140822061353/http://cens.ioc.ee/projects/f2py2e
  3249. """ % (f2py_version)
  3250. return header + pyf + footer
  3251. def _is_visit_pair(obj):
  3252. return (isinstance(obj, tuple)
  3253. and len(obj) == 2
  3254. and isinstance(obj[0], (int, str)))
  3255. def traverse(obj, visit, parents=[], result=None, *args, **kwargs):
  3256. '''Traverse f2py data structure with the following visit function:
  3257. def visit(item, parents, result, *args, **kwargs):
  3258. """
  3259. parents is a list of key-"f2py data structure" pairs from which
  3260. items are taken from.
  3261. result is a f2py data structure that is filled with the
  3262. return value of the visit function.
  3263. item is 2-tuple (index, value) if parents[-1][1] is a list
  3264. item is 2-tuple (key, value) if parents[-1][1] is a dict
  3265. The return value of visit must be None, or of the same kind as
  3266. item, that is, if parents[-1] is a list, the return value must
  3267. be 2-tuple (new_index, new_value), or if parents[-1] is a
  3268. dict, the return value must be 2-tuple (new_key, new_value).
  3269. If new_index or new_value is None, the return value of visit
  3270. is ignored, that is, it will not be added to the result.
  3271. If the return value is None, the content of obj will be
  3272. traversed, otherwise not.
  3273. """
  3274. '''
  3275. if _is_visit_pair(obj):
  3276. if obj[0] == 'parent_block':
  3277. # avoid infinite recursion
  3278. return obj
  3279. new_result = visit(obj, parents, result, *args, **kwargs)
  3280. if new_result is not None:
  3281. assert _is_visit_pair(new_result)
  3282. return new_result
  3283. parent = obj
  3284. result_key, obj = obj
  3285. else:
  3286. parent = (None, obj)
  3287. result_key = None
  3288. if isinstance(obj, list):
  3289. new_result = []
  3290. for index, value in enumerate(obj):
  3291. new_index, new_item = traverse((index, value), visit,
  3292. parents + [parent], result,
  3293. *args, **kwargs)
  3294. if new_index is not None:
  3295. new_result.append(new_item)
  3296. elif isinstance(obj, dict):
  3297. new_result = {}
  3298. for key, value in obj.items():
  3299. new_key, new_value = traverse((key, value), visit,
  3300. parents + [parent], result,
  3301. *args, **kwargs)
  3302. if new_key is not None:
  3303. new_result[new_key] = new_value
  3304. else:
  3305. new_result = obj
  3306. if result_key is None:
  3307. return new_result
  3308. return result_key, new_result
  3309. def character_backward_compatibility_hook(item, parents, result,
  3310. *args, **kwargs):
  3311. """Previously, Fortran character was incorrectly treated as
  3312. character*1. This hook fixes the usage of the corresponding
  3313. variables in `check`, `dimension`, `=`, and `callstatement`
  3314. expressions.
  3315. The usage of `char*` in `callprotoargument` expression can be left
  3316. unchanged because C `character` is C typedef of `char`, although,
  3317. new implementations should use `character*` in the corresponding
  3318. expressions.
  3319. See https://github.com/numpy/numpy/pull/19388 for more information.
  3320. """
  3321. parent_key, parent_value = parents[-1]
  3322. key, value = item
  3323. def fix_usage(varname, value):
  3324. value = re.sub(r'[*]\s*\b' + varname + r'\b', varname, value)
  3325. value = re.sub(r'\b' + varname + r'\b\s*[\[]\s*0\s*[\]]',
  3326. varname, value)
  3327. return value
  3328. if parent_key in ['dimension', 'check']:
  3329. assert parents[-3][0] == 'vars'
  3330. vars_dict = parents[-3][1]
  3331. elif key == '=':
  3332. assert parents[-2][0] == 'vars'
  3333. vars_dict = parents[-2][1]
  3334. else:
  3335. vars_dict = None
  3336. new_value = None
  3337. if vars_dict is not None:
  3338. new_value = value
  3339. for varname, vd in vars_dict.items():
  3340. if ischaracter(vd):
  3341. new_value = fix_usage(varname, new_value)
  3342. elif key == 'callstatement':
  3343. vars_dict = parents[-2][1]['vars']
  3344. new_value = value
  3345. for varname, vd in vars_dict.items():
  3346. if ischaracter(vd):
  3347. # replace all occurrences of `<varname>` with
  3348. # `&<varname>` in argument passing
  3349. new_value = re.sub(
  3350. r'(?<![&])\b' + varname + r'\b', '&' + varname, new_value)
  3351. if new_value is not None:
  3352. if new_value != value:
  3353. # We report the replacements here so that downstream
  3354. # software could update their source codes
  3355. # accordingly. However, such updates are recommended only
  3356. # when BC with numpy 1.21 or older is not required.
  3357. outmess(f'character_bc_hook[{parent_key}.{key}]:'
  3358. f' replaced `{value}` -> `{new_value}`\n', 1)
  3359. return (key, new_value)
  3360. post_processing_hooks.append(character_backward_compatibility_hook)
  3361. if __name__ == "__main__":
  3362. files = []
  3363. funcs = []
  3364. f = 1
  3365. f2 = 0
  3366. f3 = 0
  3367. showblocklist = 0
  3368. for l in sys.argv[1:]:
  3369. if l == '':
  3370. pass
  3371. elif l[0] == ':':
  3372. f = 0
  3373. elif l == '-quiet':
  3374. quiet = 1
  3375. verbose = 0
  3376. elif l == '-verbose':
  3377. verbose = 2
  3378. quiet = 0
  3379. elif l == '-fix':
  3380. if strictf77:
  3381. outmess(
  3382. 'Use option -f90 before -fix if Fortran 90 code is in fix form.\n', 0)
  3383. skipemptyends = 1
  3384. sourcecodeform = 'fix'
  3385. elif l == '-skipemptyends':
  3386. skipemptyends = 1
  3387. elif l == '--ignore-contains':
  3388. ignorecontains = 1
  3389. elif l == '-f77':
  3390. strictf77 = 1
  3391. sourcecodeform = 'fix'
  3392. elif l == '-f90':
  3393. strictf77 = 0
  3394. sourcecodeform = 'free'
  3395. skipemptyends = 1
  3396. elif l == '-h':
  3397. f2 = 1
  3398. elif l == '-show':
  3399. showblocklist = 1
  3400. elif l == '-m':
  3401. f3 = 1
  3402. elif l[0] == '-':
  3403. errmess(f'Unknown option {repr(l)}\n')
  3404. elif f2:
  3405. f2 = 0
  3406. pyffilename = l
  3407. elif f3:
  3408. f3 = 0
  3409. f77modulename = l
  3410. elif f:
  3411. try:
  3412. open(l).close()
  3413. files.append(l)
  3414. except OSError as detail:
  3415. errmess(f'OSError: {detail!s}\n')
  3416. else:
  3417. funcs.append(l)
  3418. if not strictf77 and f77modulename and not skipemptyends:
  3419. outmess("""\
  3420. Warning: You have specified module name for non Fortran 77 code that
  3421. should not need one (expect if you are scanning F90 code for non
  3422. module blocks but then you should use flag -skipemptyends and also
  3423. be sure that the files do not contain programs without program
  3424. statement).
  3425. """, 0)
  3426. postlist = crackfortran(files)
  3427. if pyffilename:
  3428. outmess(f'Writing fortran code to file {repr(pyffilename)}\n', 0)
  3429. pyf = crack2fortran(postlist)
  3430. with open(pyffilename, 'w') as f:
  3431. f.write(pyf)
  3432. if showblocklist:
  3433. show(postlist)