You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					330 lines
				
				9.3 KiB
			
		
		
			
		
	
	
					330 lines
				
				9.3 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								#!/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()
							 |