from_template.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. #!/usr/bin/env python3
  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. __all__ = ['process_str', 'process_file']
  35. import os
  36. import sys
  37. import re
  38. routine_start_re = re.compile(r'(\n|\A)(( (\$|\*))|)\s*(subroutine|function)\b', re.I)
  39. routine_end_re = re.compile(r'\n\s*end\s*(subroutine|function)\b.*(\n|\Z)', re.I)
  40. function_start_re = re.compile(r'\n (\$|\*)\s*function\b', re.I)
  41. def parse_structure(astr):
  42. """ Return a list of tuples for each function or subroutine each
  43. tuple is the start and end of a subroutine or function to be
  44. expanded.
  45. """
  46. spanlist = []
  47. ind = 0
  48. while True:
  49. m = routine_start_re.search(astr, ind)
  50. if m is None:
  51. break
  52. start = m.start()
  53. if function_start_re.match(astr, start, m.end()):
  54. while True:
  55. i = astr.rfind('\n', ind, start)
  56. if i==-1:
  57. break
  58. start = i
  59. if astr[i:i+7]!='\n $':
  60. break
  61. start += 1
  62. m = routine_end_re.search(astr, m.end())
  63. ind = end = m and m.end()-1 or len(astr)
  64. spanlist.append((start, end))
  65. return spanlist
  66. template_re = re.compile(r"<\s*(\w[\w\d]*)\s*>")
  67. named_re = re.compile(r"<\s*(\w[\w\d]*)\s*=\s*(.*?)\s*>")
  68. list_re = re.compile(r"<\s*((.*?))\s*>")
  69. def find_repl_patterns(astr):
  70. reps = named_re.findall(astr)
  71. names = {}
  72. for rep in reps:
  73. name = rep[0].strip() or unique_key(names)
  74. repl = rep[1].replace(r'\,', '@comma@')
  75. thelist = conv(repl)
  76. names[name] = thelist
  77. return names
  78. def find_and_remove_repl_patterns(astr):
  79. names = find_repl_patterns(astr)
  80. astr = re.subn(named_re, '', astr)[0]
  81. return astr, names
  82. item_re = re.compile(r"\A\\(?P<index>\d+)\Z")
  83. def conv(astr):
  84. b = astr.split(',')
  85. l = [x.strip() for x in b]
  86. for i in range(len(l)):
  87. m = item_re.match(l[i])
  88. if m:
  89. j = int(m.group('index'))
  90. l[i] = l[j]
  91. return ','.join(l)
  92. def unique_key(adict):
  93. """ Obtain a unique key given a dictionary."""
  94. allkeys = list(adict.keys())
  95. done = False
  96. n = 1
  97. while not done:
  98. newkey = '__l%s' % (n)
  99. if newkey in allkeys:
  100. n += 1
  101. else:
  102. done = True
  103. return newkey
  104. template_name_re = re.compile(r'\A\s*(\w[\w\d]*)\s*\Z')
  105. def expand_sub(substr, names):
  106. substr = substr.replace(r'\>', '@rightarrow@')
  107. substr = substr.replace(r'\<', '@leftarrow@')
  108. lnames = find_repl_patterns(substr)
  109. substr = named_re.sub(r"<\1>", substr) # get rid of definition templates
  110. def listrepl(mobj):
  111. thelist = conv(mobj.group(1).replace(r'\,', '@comma@'))
  112. if template_name_re.match(thelist):
  113. return "<%s>" % (thelist)
  114. name = None
  115. for key in lnames.keys(): # see if list is already in dictionary
  116. if lnames[key] == thelist:
  117. name = key
  118. if name is None: # this list is not in the dictionary yet
  119. name = unique_key(lnames)
  120. lnames[name] = thelist
  121. return "<%s>" % name
  122. substr = list_re.sub(listrepl, substr) # convert all lists to named templates
  123. # newnames are constructed as needed
  124. numsubs = None
  125. base_rule = None
  126. rules = {}
  127. for r in template_re.findall(substr):
  128. if r not in rules:
  129. thelist = lnames.get(r, names.get(r, None))
  130. if thelist is None:
  131. raise ValueError('No replicates found for <%s>' % (r))
  132. if r not in names and not thelist.startswith('_'):
  133. names[r] = thelist
  134. rule = [i.replace('@comma@', ',') for i in thelist.split(',')]
  135. num = len(rule)
  136. if numsubs is None:
  137. numsubs = num
  138. rules[r] = rule
  139. base_rule = r
  140. elif num == numsubs:
  141. rules[r] = rule
  142. else:
  143. print("Mismatch in number of replacements (base <%s=%s>)"
  144. " for <%s=%s>. Ignoring." %
  145. (base_rule, ','.join(rules[base_rule]), r, thelist))
  146. if not rules:
  147. return substr
  148. def namerepl(mobj):
  149. name = mobj.group(1)
  150. return rules.get(name, (k+1)*[name])[k]
  151. newstr = ''
  152. for k in range(numsubs):
  153. newstr += template_re.sub(namerepl, substr) + '\n\n'
  154. newstr = newstr.replace('@rightarrow@', '>')
  155. newstr = newstr.replace('@leftarrow@', '<')
  156. return newstr
  157. def process_str(allstr):
  158. newstr = allstr
  159. writestr = ''
  160. struct = parse_structure(newstr)
  161. oldend = 0
  162. names = {}
  163. names.update(_special_names)
  164. for sub in struct:
  165. cleanedstr, defs = find_and_remove_repl_patterns(newstr[oldend:sub[0]])
  166. writestr += cleanedstr
  167. names.update(defs)
  168. writestr += expand_sub(newstr[sub[0]:sub[1]], names)
  169. oldend = sub[1]
  170. writestr += newstr[oldend:]
  171. return writestr
  172. include_src_re = re.compile(r"(\n|\A)\s*include\s*['\"](?P<name>[\w\d./\\]+[.]src)['\"]", re.I)
  173. def resolve_includes(source):
  174. d = os.path.dirname(source)
  175. with open(source) as fid:
  176. lines = []
  177. for line in fid:
  178. m = include_src_re.match(line)
  179. if m:
  180. fn = m.group('name')
  181. if not os.path.isabs(fn):
  182. fn = os.path.join(d, fn)
  183. if os.path.isfile(fn):
  184. print('Including file', fn)
  185. lines.extend(resolve_includes(fn))
  186. else:
  187. lines.append(line)
  188. else:
  189. lines.append(line)
  190. return lines
  191. def process_file(source):
  192. lines = resolve_includes(source)
  193. return process_str(''.join(lines))
  194. _special_names = find_repl_patterns('''
  195. <_c=s,d,c,z>
  196. <_t=real,double precision,complex,double complex>
  197. <prefix=s,d,c,z>
  198. <ftype=real,double precision,complex,double complex>
  199. <ctype=float,double,complex_float,complex_double>
  200. <ftypereal=real,double precision,\\0,\\1>
  201. <ctypereal=float,double,\\0,\\1>
  202. ''')
  203. def main():
  204. try:
  205. file = sys.argv[1]
  206. except IndexError:
  207. fid = sys.stdin
  208. outfile = sys.stdout
  209. else:
  210. fid = open(file, 'r')
  211. (base, ext) = os.path.splitext(file)
  212. newname = base
  213. outfile = open(newname, 'w')
  214. allstr = fid.read()
  215. writestr = process_str(allstr)
  216. outfile.write(writestr)
  217. if __name__ == "__main__":
  218. main()