conv_template.py 9.3 KB

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