Chris@87: #!/usr/bin/python Chris@87: """ Chris@87: takes templated file .xxx.src and produces .xxx file where .xxx is Chris@87: .i or .c or .h, using the following template rules Chris@87: Chris@87: /**begin repeat -- on a line by itself marks the start of a repeated code Chris@87: segment Chris@87: /**end repeat**/ -- on a line by itself marks it's end Chris@87: Chris@87: After the /**begin repeat and before the */, all the named templates are placed Chris@87: these should all have the same number of replacements Chris@87: Chris@87: Repeat blocks can be nested, with each nested block labeled with its depth, Chris@87: i.e. Chris@87: /**begin repeat1 Chris@87: *.... Chris@87: */ Chris@87: /**end repeat1**/ Chris@87: Chris@87: When using nested loops, you can optionally exlude particular Chris@87: combinations of the variables using (inside the comment portion of the inner loop): Chris@87: Chris@87: :exclude: var1=value1, var2=value2, ... Chris@87: Chris@87: This will exlude the pattern where var1 is value1 and var2 is value2 when Chris@87: the result is being generated. Chris@87: Chris@87: Chris@87: In the main body each replace will use one entry from the list of named replacements Chris@87: Chris@87: Note that all #..# forms in a block must have the same number of Chris@87: comma-separated entries. Chris@87: Chris@87: Example: Chris@87: Chris@87: An input file containing Chris@87: Chris@87: /**begin repeat Chris@87: * #a = 1,2,3# Chris@87: * #b = 1,2,3# Chris@87: */ Chris@87: Chris@87: /**begin repeat1 Chris@87: * #c = ted, jim# Chris@87: */ Chris@87: @a@, @b@, @c@ Chris@87: /**end repeat1**/ Chris@87: Chris@87: /**end repeat**/ Chris@87: Chris@87: produces Chris@87: Chris@87: line 1 "template.c.src" Chris@87: Chris@87: /* Chris@87: ********************************************************************* Chris@87: ** This file was autogenerated from a template DO NOT EDIT!!** Chris@87: ** Changes should be made to the original source (.src) file ** Chris@87: ********************************************************************* Chris@87: */ Chris@87: Chris@87: #line 9 Chris@87: 1, 1, ted Chris@87: Chris@87: #line 9 Chris@87: 1, 1, jim Chris@87: Chris@87: #line 9 Chris@87: 2, 2, ted Chris@87: Chris@87: #line 9 Chris@87: 2, 2, jim Chris@87: Chris@87: #line 9 Chris@87: 3, 3, ted Chris@87: Chris@87: #line 9 Chris@87: 3, 3, jim Chris@87: Chris@87: """ Chris@87: from __future__ import division, absolute_import, print_function Chris@87: Chris@87: Chris@87: __all__ = ['process_str', 'process_file'] Chris@87: Chris@87: import os Chris@87: import sys Chris@87: import re Chris@87: Chris@87: from numpy.distutils.compat import get_exception Chris@87: Chris@87: # names for replacement that are already global. Chris@87: global_names = {} Chris@87: Chris@87: # header placed at the front of head processed file Chris@87: header =\ Chris@87: """ Chris@87: /* Chris@87: ***************************************************************************** Chris@87: ** This file was autogenerated from a template DO NOT EDIT!!!! ** Chris@87: ** Changes should be made to the original source (.src) file ** Chris@87: ***************************************************************************** Chris@87: */ Chris@87: Chris@87: """ Chris@87: # Parse string for repeat loops Chris@87: def parse_structure(astr, level): Chris@87: """ Chris@87: The returned line number is from the beginning of the string, starting Chris@87: at zero. Returns an empty list if no loops found. Chris@87: Chris@87: """ Chris@87: if level == 0 : Chris@87: loopbeg = "/**begin repeat" Chris@87: loopend = "/**end repeat**/" Chris@87: else : Chris@87: loopbeg = "/**begin repeat%d" % level Chris@87: loopend = "/**end repeat%d**/" % level Chris@87: Chris@87: ind = 0 Chris@87: line = 0 Chris@87: spanlist = [] Chris@87: while True: Chris@87: start = astr.find(loopbeg, ind) Chris@87: if start == -1: Chris@87: break Chris@87: start2 = astr.find("*/", start) Chris@87: start2 = astr.find("\n", start2) Chris@87: fini1 = astr.find(loopend, start2) Chris@87: fini2 = astr.find("\n", fini1) Chris@87: line += astr.count("\n", ind, start2+1) Chris@87: spanlist.append((start, start2+1, fini1, fini2+1, line)) Chris@87: line += astr.count("\n", start2+1, fini2) Chris@87: ind = fini2 Chris@87: spanlist.sort() Chris@87: return spanlist Chris@87: Chris@87: Chris@87: def paren_repl(obj): Chris@87: torep = obj.group(1) Chris@87: numrep = obj.group(2) Chris@87: return ','.join([torep]*int(numrep)) Chris@87: Chris@87: parenrep = re.compile(r"[(]([^)]*)[)]\*(\d+)") Chris@87: plainrep = re.compile(r"([^*]+)\*(\d+)") Chris@87: def parse_values(astr): Chris@87: # replaces all occurrences of '(a,b,c)*4' in astr Chris@87: # with 'a,b,c,a,b,c,a,b,c,a,b,c'. Empty braces generate Chris@87: # empty values, i.e., ()*4 yields ',,,'. The result is Chris@87: # split at ',' and a list of values returned. Chris@87: astr = parenrep.sub(paren_repl, astr) Chris@87: # replaces occurences of xxx*3 with xxx, xxx, xxx Chris@87: astr = ','.join([plainrep.sub(paren_repl, x.strip()) Chris@87: for x in astr.split(',')]) Chris@87: return astr.split(',') Chris@87: Chris@87: Chris@87: stripast = re.compile(r"\n\s*\*?") Chris@87: named_re = re.compile(r"#\s*(\w*)\s*=([^#]*)#") Chris@87: exclude_vars_re = re.compile(r"(\w*)=(\w*)") Chris@87: exclude_re = re.compile(":exclude:") Chris@87: def parse_loop_header(loophead) : Chris@87: """Find all named replacements in the header Chris@87: Chris@87: Returns a list of dictionaries, one for each loop iteration, Chris@87: where each key is a name to be substituted and the corresponding Chris@87: value is the replacement string. Chris@87: Chris@87: Also return a list of exclusions. The exclusions are dictionaries Chris@87: of key value pairs. There can be more than one exclusion. Chris@87: [{'var1':'value1', 'var2', 'value2'[,...]}, ...] Chris@87: Chris@87: """ Chris@87: # Strip out '\n' and leading '*', if any, in continuation lines. Chris@87: # This should not effect code previous to this change as Chris@87: # continuation lines were not allowed. Chris@87: loophead = stripast.sub("", loophead) Chris@87: # parse out the names and lists of values Chris@87: names = [] Chris@87: reps = named_re.findall(loophead) Chris@87: nsub = None Chris@87: for rep in reps: Chris@87: name = rep[0] Chris@87: vals = parse_values(rep[1]) Chris@87: size = len(vals) Chris@87: if nsub is None : Chris@87: nsub = size Chris@87: elif nsub != size : Chris@87: msg = "Mismatch in number of values:\n%s = %s" % (name, vals) Chris@87: raise ValueError(msg) Chris@87: names.append((name, vals)) Chris@87: Chris@87: Chris@87: # Find any exclude variables Chris@87: excludes = [] Chris@87: Chris@87: for obj in exclude_re.finditer(loophead): Chris@87: span = obj.span() Chris@87: # find next newline Chris@87: endline = loophead.find('\n', span[1]) Chris@87: substr = loophead[span[1]:endline] Chris@87: ex_names = exclude_vars_re.findall(substr) Chris@87: excludes.append(dict(ex_names)) Chris@87: Chris@87: # generate list of dictionaries, one for each template iteration Chris@87: dlist = [] Chris@87: if nsub is None : Chris@87: raise ValueError("No substitution variables found") Chris@87: for i in range(nsub) : Chris@87: tmp = {} Chris@87: for name, vals in names : Chris@87: tmp[name] = vals[i] Chris@87: dlist.append(tmp) Chris@87: return dlist Chris@87: Chris@87: replace_re = re.compile(r"@([\w]+)@") Chris@87: def parse_string(astr, env, level, line) : Chris@87: lineno = "#line %d\n" % line Chris@87: Chris@87: # local function for string replacement, uses env Chris@87: def replace(match): Chris@87: name = match.group(1) Chris@87: try : Chris@87: val = env[name] Chris@87: except KeyError: Chris@87: msg = 'line %d: no definition of key "%s"'%(line, name) Chris@87: raise ValueError(msg) Chris@87: return val Chris@87: Chris@87: code = [lineno] Chris@87: struct = parse_structure(astr, level) Chris@87: if struct : Chris@87: # recurse over inner loops Chris@87: oldend = 0 Chris@87: newlevel = level + 1 Chris@87: for sub in struct: Chris@87: pref = astr[oldend:sub[0]] Chris@87: head = astr[sub[0]:sub[1]] Chris@87: text = astr[sub[1]:sub[2]] Chris@87: oldend = sub[3] Chris@87: newline = line + sub[4] Chris@87: code.append(replace_re.sub(replace, pref)) Chris@87: try : Chris@87: envlist = parse_loop_header(head) Chris@87: except ValueError: Chris@87: e = get_exception() Chris@87: msg = "line %d: %s" % (newline, e) Chris@87: raise ValueError(msg) Chris@87: for newenv in envlist : Chris@87: newenv.update(env) Chris@87: newcode = parse_string(text, newenv, newlevel, newline) Chris@87: code.extend(newcode) Chris@87: suff = astr[oldend:] Chris@87: code.append(replace_re.sub(replace, suff)) Chris@87: else : Chris@87: # replace keys Chris@87: code.append(replace_re.sub(replace, astr)) Chris@87: code.append('\n') Chris@87: return ''.join(code) Chris@87: Chris@87: def process_str(astr): Chris@87: code = [header] Chris@87: code.extend(parse_string(astr, global_names, 0, 1)) Chris@87: return ''.join(code) Chris@87: Chris@87: Chris@87: include_src_re = re.compile(r"(\n|\A)#include\s*['\"]" Chris@87: r"(?P[\w\d./\\]+[.]src)['\"]", re.I) Chris@87: Chris@87: def resolve_includes(source): Chris@87: d = os.path.dirname(source) Chris@87: fid = open(source) Chris@87: lines = [] Chris@87: for line in fid: Chris@87: m = include_src_re.match(line) Chris@87: if m: Chris@87: fn = m.group('name') Chris@87: if not os.path.isabs(fn): Chris@87: fn = os.path.join(d, fn) Chris@87: if os.path.isfile(fn): Chris@87: print('Including file', fn) Chris@87: lines.extend(resolve_includes(fn)) Chris@87: else: Chris@87: lines.append(line) Chris@87: else: Chris@87: lines.append(line) Chris@87: fid.close() Chris@87: return lines Chris@87: Chris@87: def process_file(source): Chris@87: lines = resolve_includes(source) Chris@87: sourcefile = os.path.normcase(source).replace("\\", "\\\\") Chris@87: try: Chris@87: code = process_str(''.join(lines)) Chris@87: except ValueError: Chris@87: e = get_exception() Chris@87: raise ValueError('In "%s" loop at %s' % (sourcefile, e)) Chris@87: return '#line 1 "%s"\n%s' % (sourcefile, code) Chris@87: Chris@87: Chris@87: def unique_key(adict): Chris@87: # this obtains a unique key given a dictionary Chris@87: # currently it works by appending together n of the letters of the Chris@87: # current keys and increasing n until a unique key is found Chris@87: # -- not particularly quick Chris@87: allkeys = list(adict.keys()) Chris@87: done = False Chris@87: n = 1 Chris@87: while not done: Chris@87: newkey = "".join([x[:n] for x in allkeys]) Chris@87: if newkey in allkeys: Chris@87: n += 1 Chris@87: else: Chris@87: done = True Chris@87: return newkey Chris@87: Chris@87: Chris@87: if __name__ == "__main__": Chris@87: Chris@87: try: Chris@87: file = sys.argv[1] Chris@87: except IndexError: Chris@87: fid = sys.stdin Chris@87: outfile = sys.stdout Chris@87: else: Chris@87: fid = open(file, 'r') Chris@87: (base, ext) = os.path.splitext(file) Chris@87: newname = base Chris@87: outfile = open(newname, 'w') Chris@87: Chris@87: allstr = fid.read() Chris@87: try: Chris@87: writestr = process_str(allstr) Chris@87: except ValueError: Chris@87: e = get_exception() Chris@87: raise ValueError("In %s loop at %s" % (file, e)) Chris@87: outfile.write(writestr)