crackfortran.py 145 KB

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