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