from_template.py 7.8 KB

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