crackfortran.py 145 KB

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