_src_pyf.py 7.5 KB

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