conv_template.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. #!/usr/bin/env python
  2. """
  3. takes templated file .xxx.src and produces .xxx file where .xxx is
  4. .i or .c or .h, using the following template rules
  5. /**begin repeat -- on a line by itself marks the start of a repeated code
  6. segment
  7. /**end repeat**/ -- on a line by itself marks it's end
  8. After the /**begin repeat and before the */, all the named templates are placed
  9. these should all have the same number of replacements
  10. Repeat blocks can be nested, with each nested block labeled with its depth,
  11. i.e.
  12. /**begin repeat1
  13. *....
  14. */
  15. /**end repeat1**/
  16. When using nested loops, you can optionally exclude particular
  17. combinations of the variables using (inside the comment portion of the inner loop):
  18. :exclude: var1=value1, var2=value2, ...
  19. This will exclude the pattern where var1 is value1 and var2 is value2 when
  20. the result is being generated.
  21. In the main body each replace will use one entry from the list of named replacements
  22. Note that all #..# forms in a block must have the same number of
  23. comma-separated entries.
  24. Example:
  25. An input file containing
  26. /**begin repeat
  27. * #a = 1,2,3#
  28. * #b = 1,2,3#
  29. */
  30. /**begin repeat1
  31. * #c = ted, jim#
  32. */
  33. @a@, @b@, @c@
  34. /**end repeat1**/
  35. /**end repeat**/
  36. produces
  37. line 1 "template.c.src"
  38. /*
  39. *********************************************************************
  40. ** This file was autogenerated from a template DO NOT EDIT!!**
  41. ** Changes should be made to the original source (.src) file **
  42. *********************************************************************
  43. */
  44. #line 9
  45. 1, 1, ted
  46. #line 9
  47. 1, 1, jim
  48. #line 9
  49. 2, 2, ted
  50. #line 9
  51. 2, 2, jim
  52. #line 9
  53. 3, 3, ted
  54. #line 9
  55. 3, 3, jim
  56. """
  57. from __future__ import division, absolute_import, print_function
  58. __all__ = ['process_str', 'process_file']
  59. import os
  60. import sys
  61. import re
  62. from numpy.distutils.compat import get_exception
  63. # names for replacement that are already global.
  64. global_names = {}
  65. # header placed at the front of head processed file
  66. header =\
  67. """
  68. /*
  69. *****************************************************************************
  70. ** This file was autogenerated from a template DO NOT EDIT!!!! **
  71. ** Changes should be made to the original source (.src) file **
  72. *****************************************************************************
  73. */
  74. """
  75. # Parse string for repeat loops
  76. def parse_structure(astr, level):
  77. """
  78. The returned line number is from the beginning of the string, starting
  79. at zero. Returns an empty list if no loops found.
  80. """
  81. if level == 0 :
  82. loopbeg = "/**begin repeat"
  83. loopend = "/**end repeat**/"
  84. else :
  85. loopbeg = "/**begin repeat%d" % level
  86. loopend = "/**end repeat%d**/" % level
  87. ind = 0
  88. line = 0
  89. spanlist = []
  90. while True:
  91. start = astr.find(loopbeg, ind)
  92. if start == -1:
  93. break
  94. start2 = astr.find("*/", start)
  95. start2 = astr.find("\n", start2)
  96. fini1 = astr.find(loopend, start2)
  97. fini2 = astr.find("\n", fini1)
  98. line += astr.count("\n", ind, start2+1)
  99. spanlist.append((start, start2+1, fini1, fini2+1, line))
  100. line += astr.count("\n", start2+1, fini2)
  101. ind = fini2
  102. spanlist.sort()
  103. return spanlist
  104. def paren_repl(obj):
  105. torep = obj.group(1)
  106. numrep = obj.group(2)
  107. return ','.join([torep]*int(numrep))
  108. parenrep = re.compile(r"[(]([^)]*)[)]\*(\d+)")
  109. plainrep = re.compile(r"([^*]+)\*(\d+)")
  110. def parse_values(astr):
  111. # replaces all occurrences of '(a,b,c)*4' in astr
  112. # with 'a,b,c,a,b,c,a,b,c,a,b,c'. Empty braces generate
  113. # empty values, i.e., ()*4 yields ',,,'. The result is
  114. # split at ',' and a list of values returned.
  115. astr = parenrep.sub(paren_repl, astr)
  116. # replaces occurrences of xxx*3 with xxx, xxx, xxx
  117. astr = ','.join([plainrep.sub(paren_repl, x.strip())
  118. for x in astr.split(',')])
  119. return astr.split(',')
  120. stripast = re.compile(r"\n\s*\*?")
  121. named_re = re.compile(r"#\s*(\w*)\s*=([^#]*)#")
  122. exclude_vars_re = re.compile(r"(\w*)=(\w*)")
  123. exclude_re = re.compile(":exclude:")
  124. def parse_loop_header(loophead) :
  125. """Find all named replacements in the header
  126. Returns a list of dictionaries, one for each loop iteration,
  127. where each key is a name to be substituted and the corresponding
  128. value is the replacement string.
  129. Also return a list of exclusions. The exclusions are dictionaries
  130. of key value pairs. There can be more than one exclusion.
  131. [{'var1':'value1', 'var2', 'value2'[,...]}, ...]
  132. """
  133. # Strip out '\n' and leading '*', if any, in continuation lines.
  134. # This should not effect code previous to this change as
  135. # continuation lines were not allowed.
  136. loophead = stripast.sub("", loophead)
  137. # parse out the names and lists of values
  138. names = []
  139. reps = named_re.findall(loophead)
  140. nsub = None
  141. for rep in reps:
  142. name = rep[0]
  143. vals = parse_values(rep[1])
  144. size = len(vals)
  145. if nsub is None :
  146. nsub = size
  147. elif nsub != size :
  148. msg = "Mismatch in number of values, %d != %d\n%s = %s"
  149. raise ValueError(msg % (nsub, size, name, vals))
  150. names.append((name, vals))
  151. # Find any exclude variables
  152. excludes = []
  153. for obj in exclude_re.finditer(loophead):
  154. span = obj.span()
  155. # find next newline
  156. endline = loophead.find('\n', span[1])
  157. substr = loophead[span[1]:endline]
  158. ex_names = exclude_vars_re.findall(substr)
  159. excludes.append(dict(ex_names))
  160. # generate list of dictionaries, one for each template iteration
  161. dlist = []
  162. if nsub is None :
  163. raise ValueError("No substitution variables found")
  164. for i in range(nsub):
  165. tmp = {name: vals[i] for name, vals in names}
  166. dlist.append(tmp)
  167. return dlist
  168. replace_re = re.compile(r"@([\w]+)@")
  169. def parse_string(astr, env, level, line) :
  170. lineno = "#line %d\n" % line
  171. # local function for string replacement, uses env
  172. def replace(match):
  173. name = match.group(1)
  174. try :
  175. val = env[name]
  176. except KeyError:
  177. msg = 'line %d: no definition of key "%s"'%(line, name)
  178. raise ValueError(msg)
  179. return val
  180. code = [lineno]
  181. struct = parse_structure(astr, level)
  182. if struct :
  183. # recurse over inner loops
  184. oldend = 0
  185. newlevel = level + 1
  186. for sub in struct:
  187. pref = astr[oldend:sub[0]]
  188. head = astr[sub[0]:sub[1]]
  189. text = astr[sub[1]:sub[2]]
  190. oldend = sub[3]
  191. newline = line + sub[4]
  192. code.append(replace_re.sub(replace, pref))
  193. try :
  194. envlist = parse_loop_header(head)
  195. except ValueError:
  196. e = get_exception()
  197. msg = "line %d: %s" % (newline, e)
  198. raise ValueError(msg)
  199. for newenv in envlist :
  200. newenv.update(env)
  201. newcode = parse_string(text, newenv, newlevel, newline)
  202. code.extend(newcode)
  203. suff = astr[oldend:]
  204. code.append(replace_re.sub(replace, suff))
  205. else :
  206. # replace keys
  207. code.append(replace_re.sub(replace, astr))
  208. code.append('\n')
  209. return ''.join(code)
  210. def process_str(astr):
  211. code = [header]
  212. code.extend(parse_string(astr, global_names, 0, 1))
  213. return ''.join(code)
  214. include_src_re = re.compile(r"(\n|\A)#include\s*['\"]"
  215. r"(?P<name>[\w\d./\\]+[.]src)['\"]", re.I)
  216. def resolve_includes(source):
  217. d = os.path.dirname(source)
  218. with open(source) as fid:
  219. lines = []
  220. for line in fid:
  221. m = include_src_re.match(line)
  222. if m:
  223. fn = m.group('name')
  224. if not os.path.isabs(fn):
  225. fn = os.path.join(d, fn)
  226. if os.path.isfile(fn):
  227. print('Including file', fn)
  228. lines.extend(resolve_includes(fn))
  229. else:
  230. lines.append(line)
  231. else:
  232. lines.append(line)
  233. return lines
  234. def process_file(source):
  235. lines = resolve_includes(source)
  236. sourcefile = os.path.normcase(source).replace("\\", "\\\\")
  237. try:
  238. code = process_str(''.join(lines))
  239. except ValueError:
  240. e = get_exception()
  241. raise ValueError('In "%s" loop at %s' % (sourcefile, e))
  242. return '#line 1 "%s"\n%s' % (sourcefile, code)
  243. def unique_key(adict):
  244. # this obtains a unique key given a dictionary
  245. # currently it works by appending together n of the letters of the
  246. # current keys and increasing n until a unique key is found
  247. # -- not particularly quick
  248. allkeys = list(adict.keys())
  249. done = False
  250. n = 1
  251. while not done:
  252. newkey = "".join([x[:n] for x in allkeys])
  253. if newkey in allkeys:
  254. n += 1
  255. else:
  256. done = True
  257. return newkey
  258. def main():
  259. try:
  260. file = sys.argv[1]
  261. except IndexError:
  262. fid = sys.stdin
  263. outfile = sys.stdout
  264. else:
  265. fid = open(file, 'r')
  266. (base, ext) = os.path.splitext(file)
  267. newname = base
  268. outfile = open(newname, 'w')
  269. allstr = fid.read()
  270. try:
  271. writestr = process_str(allstr)
  272. except ValueError:
  273. e = get_exception()
  274. raise ValueError("In %s loop at %s" % (file, e))
  275. outfile.write(writestr)
  276. if __name__ == "__main__":
  277. main()