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