You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

generate_f2pymod.py 9.4 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. #!/usr/bin/env python3
  2. """
  3. Process f2py template files (`filename.pyf.src` -> `filename.pyf`)
  4. Usage: python generate_pyf.py filename.pyf.src -o filename.pyf
  5. """
  6. import os
  7. import sys
  8. import re
  9. import subprocess
  10. import argparse
  11. # START OF CODE VENDORED FROM `numpy.distutils.from_template`
  12. #############################################################
  13. """
  14. process_file(filename)
  15. takes templated file .xxx.src and produces .xxx file where .xxx
  16. is .pyf .f90 or .f using the following template rules:
  17. '<..>' denotes a template.
  18. All function and subroutine blocks in a source file with names that
  19. contain '<..>' will be replicated according to the rules in '<..>'.
  20. The number of comma-separated words in '<..>' will determine the number of
  21. replicates.
  22. '<..>' may have two different forms, named and short. For example,
  23. named:
  24. <p=d,s,z,c> where anywhere inside a block '<p>' will be replaced with
  25. 'd', 's', 'z', and 'c' for each replicate of the block.
  26. <_c> is already defined: <_c=s,d,c,z>
  27. <_t> is already defined: <_t=real,double precision,complex,double complex>
  28. short:
  29. <s,d,c,z>, a short form of the named, useful when no <p> appears inside
  30. a block.
  31. In general, '<..>' contains a comma separated list of arbitrary
  32. expressions. If these expression must contain a comma|leftarrow|rightarrow,
  33. then prepend the comma|leftarrow|rightarrow with a backslash.
  34. If an expression matches '\\<index>' then it will be replaced
  35. by <index>-th expression.
  36. Note that all '<..>' forms in a block must have the same number of
  37. comma-separated entries.
  38. Predefined named template rules:
  39. <prefix=s,d,c,z>
  40. <ftype=real,double precision,complex,double complex>
  41. <ftypereal=real,double precision,\\0,\\1>
  42. <ctype=float,double,complex_float,complex_double>
  43. <ctypereal=float,double,\\0,\\1>
  44. """
  45. routine_start_re = re.compile(
  46. r'(\n|\A)(( (\$|\*))|)\s*(subroutine|function)\b',
  47. re.I
  48. )
  49. routine_end_re = re.compile(r'\n\s*end\s*(subroutine|function)\b.*(\n|\Z)', re.I)
  50. function_start_re = re.compile(r'\n (\$|\*)\s*function\b', re.I)
  51. def parse_structure(astr):
  52. """ Return a list of tuples for each function or subroutine each
  53. tuple is the start and end of a subroutine or function to be
  54. expanded.
  55. """
  56. spanlist = []
  57. ind = 0
  58. while True:
  59. m = routine_start_re.search(astr, ind)
  60. if m is None:
  61. break
  62. start = m.start()
  63. if function_start_re.match(astr, start, m.end()):
  64. while True:
  65. i = astr.rfind('\n', ind, start)
  66. if i==-1:
  67. break
  68. start = i
  69. if astr[i:i+7]!='\n $':
  70. break
  71. start += 1
  72. m = routine_end_re.search(astr, m.end())
  73. ind = end = m and m.end()-1 or len(astr)
  74. spanlist.append((start, end))
  75. return spanlist
  76. template_re = re.compile(r"<\s*(\w[\w\d]*)\s*>")
  77. named_re = re.compile(r"<\s*(\w[\w\d]*)\s*=\s*(.*?)\s*>")
  78. list_re = re.compile(r"<\s*((.*?))\s*>")
  79. def find_repl_patterns(astr):
  80. reps = named_re.findall(astr)
  81. names = {}
  82. for rep in reps:
  83. name = rep[0].strip() or unique_key(names)
  84. repl = rep[1].replace(r'\,', '@comma@')
  85. thelist = conv(repl)
  86. names[name] = thelist
  87. return names
  88. def find_and_remove_repl_patterns(astr):
  89. names = find_repl_patterns(astr)
  90. astr = re.subn(named_re, '', astr)[0]
  91. return astr, names
  92. item_re = re.compile(r"\A\\(?P<index>\d+)\Z")
  93. def conv(astr):
  94. b = astr.split(',')
  95. l = [x.strip() for x in b]
  96. for i in range(len(l)):
  97. m = item_re.match(l[i])
  98. if m:
  99. j = int(m.group('index'))
  100. l[i] = l[j]
  101. return ','.join(l)
  102. def unique_key(adict):
  103. """ Obtain a unique key given a dictionary."""
  104. allkeys = list(adict.keys())
  105. done = False
  106. n = 1
  107. while not done:
  108. newkey = '__l%s' % (n)
  109. if newkey in allkeys:
  110. n += 1
  111. else:
  112. done = True
  113. return newkey
  114. template_name_re = re.compile(r'\A\s*(\w[\w\d]*)\s*\Z')
  115. def expand_sub(substr, names):
  116. substr = substr.replace(r'\>', '@rightarrow@')
  117. substr = substr.replace(r'\<', '@leftarrow@')
  118. lnames = find_repl_patterns(substr)
  119. substr = named_re.sub(r"<\1>", substr) # get rid of definition templates
  120. def listrepl(mobj):
  121. thelist = conv(mobj.group(1).replace(r'\,', '@comma@'))
  122. if template_name_re.match(thelist):
  123. return "<%s>" % (thelist)
  124. name = None
  125. for key in lnames.keys(): # see if list is already in dictionary
  126. if lnames[key] == thelist:
  127. name = key
  128. if name is None: # this list is not in the dictionary yet
  129. name = unique_key(lnames)
  130. lnames[name] = thelist
  131. return "<%s>" % name
  132. substr = list_re.sub(listrepl, substr) # convert all lists to named templates
  133. # newnames are constructed as needed
  134. numsubs = None
  135. base_rule = None
  136. rules = {}
  137. for r in template_re.findall(substr):
  138. if r not in rules:
  139. thelist = lnames.get(r, names.get(r, None))
  140. if thelist is None:
  141. raise ValueError('No replicates found for <%s>' % (r))
  142. if r not in names and not thelist.startswith('_'):
  143. names[r] = thelist
  144. rule = [i.replace('@comma@', ',') for i in thelist.split(',')]
  145. num = len(rule)
  146. if numsubs is None:
  147. numsubs = num
  148. rules[r] = rule
  149. base_rule = r
  150. elif num == numsubs:
  151. rules[r] = rule
  152. else:
  153. print("Mismatch in number of replacements (base <{}={}>) "
  154. "for <{}={}>. Ignoring."
  155. .format(base_rule, ','.join(rules[base_rule]), r, thelist))
  156. if not rules:
  157. return substr
  158. def namerepl(mobj):
  159. name = mobj.group(1)
  160. return rules.get(name, (k+1)*[name])[k]
  161. newstr = ''
  162. for k in range(numsubs):
  163. newstr += template_re.sub(namerepl, substr) + '\n\n'
  164. newstr = newstr.replace('@rightarrow@', '>')
  165. newstr = newstr.replace('@leftarrow@', '<')
  166. return newstr
  167. def process_str(allstr):
  168. newstr = allstr
  169. writestr = ''
  170. struct = parse_structure(newstr)
  171. oldend = 0
  172. names = {}
  173. names.update(_special_names)
  174. for sub in struct:
  175. cleanedstr, defs = find_and_remove_repl_patterns(newstr[oldend:sub[0]])
  176. writestr += cleanedstr
  177. names.update(defs)
  178. writestr += expand_sub(newstr[sub[0]:sub[1]], names)
  179. oldend = sub[1]
  180. writestr += newstr[oldend:]
  181. return writestr
  182. include_src_re = re.compile(
  183. r"(\n|\A)\s*include\s*['\"](?P<name>[\w\d./\\]+\.src)['\"]",
  184. re.I
  185. )
  186. def resolve_includes(source):
  187. d = os.path.dirname(source)
  188. with open(source) as fid:
  189. lines = []
  190. for line in fid:
  191. m = include_src_re.match(line)
  192. if m:
  193. fn = m.group('name')
  194. if not os.path.isabs(fn):
  195. fn = os.path.join(d, fn)
  196. if os.path.isfile(fn):
  197. lines.extend(resolve_includes(fn))
  198. else:
  199. lines.append(line)
  200. else:
  201. lines.append(line)
  202. return lines
  203. def process_file(source):
  204. lines = resolve_includes(source)
  205. return process_str(''.join(lines))
  206. _special_names = find_repl_patterns('''
  207. <_c=s,d,c,z>
  208. <_t=real,double precision,complex,double complex>
  209. <prefix=s,d,c,z>
  210. <ftype=real,double precision,complex,double complex>
  211. <ctype=float,double,complex_float,complex_double>
  212. <ftypereal=real,double precision,\\0,\\1>
  213. <ctypereal=float,double,\\0,\\1>
  214. ''')
  215. # END OF CODE VENDORED FROM `numpy.distutils.from_template`
  216. ###########################################################
  217. def main():
  218. parser = argparse.ArgumentParser()
  219. parser.add_argument("infile", type=str,
  220. help="Path to the input file")
  221. parser.add_argument("-o", "--outdir", type=str,
  222. help="Path to the output directory")
  223. args = parser.parse_args()
  224. if not args.infile.endswith(('.pyf', '.pyf.src', '.f.src')):
  225. raise ValueError(f"Input file has unknown extension: {args.infile}")
  226. outdir_abs = os.path.join(os.getcwd(), args.outdir)
  227. # Write out the .pyf/.f file
  228. if args.infile.endswith(('.pyf.src', '.f.src')):
  229. code = process_file(args.infile)
  230. fname_pyf = os.path.join(args.outdir,
  231. os.path.splitext(os.path.split(args.infile)[1])[0])
  232. with open(fname_pyf, 'w') as f:
  233. f.write(code)
  234. else:
  235. fname_pyf = args.infile
  236. # Now invoke f2py to generate the C API module file
  237. if args.infile.endswith(('.pyf.src', '.pyf')):
  238. p = subprocess.Popen([sys.executable, '-m', 'numpy.f2py', fname_pyf,
  239. '--build-dir', outdir_abs], #'--quiet'],
  240. stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  241. cwd=os.getcwd())
  242. out, err = p.communicate()
  243. if not (p.returncode == 0):
  244. raise RuntimeError(f"Writing {args.outfile} with f2py failed!\n"
  245. f"{out}\n"
  246. r"{err}")
  247. if __name__ == "__main__":
  248. main()