| #!/usr/bin/env python3 |
| """ |
| |
| process_file(filename) |
| |
| takes templated file .xxx.src and produces .xxx file where .xxx |
| is .pyf .f90 or .f using the following template rules: |
| |
| '<..>' denotes a template. |
| |
| All function and subroutine blocks in a source file with names that |
| contain '<..>' will be replicated according to the rules in '<..>'. |
| |
| The number of comma-separated words in '<..>' will determine the number of |
| replicates. |
| |
| '<..>' may have two different forms, named and short. For example, |
| |
| named: |
| <p=d,s,z,c> where anywhere inside a block '<p>' will be replaced with |
| 'd', 's', 'z', and 'c' for each replicate of the block. |
| |
| <_c> is already defined: <_c=s,d,c,z> |
| <_t> is already defined: <_t=real,double precision,complex,double complex> |
| |
| short: |
| <s,d,c,z>, a short form of the named, useful when no <p> appears inside |
| a block. |
| |
| In general, '<..>' contains a comma separated list of arbitrary |
| expressions. If these expression must contain a comma|leftarrow|rightarrow, |
| then prepend the comma|leftarrow|rightarrow with a backslash. |
| |
| If an expression matches '\\<index>' then it will be replaced |
| by <index>-th expression. |
| |
| Note that all '<..>' forms in a block must have the same number of |
| comma-separated entries. |
| |
| Predefined named template rules: |
| <prefix=s,d,c,z> |
| <ftype=real,double precision,complex,double complex> |
| <ftypereal=real,double precision,\\0,\\1> |
| <ctype=float,double,complex_float,complex_double> |
| <ctypereal=float,double,\\0,\\1> |
| |
| """ |
| __all__ = ['process_str', 'process_file'] |
| |
| import os |
| import sys |
| import re |
| |
| routine_start_re = re.compile(r'(\n|\A)(( (\$|\*))|)\s*(subroutine|function)\b', re.I) |
| routine_end_re = re.compile(r'\n\s*end\s*(subroutine|function)\b.*(\n|\Z)', re.I) |
| function_start_re = re.compile(r'\n (\$|\*)\s*function\b', re.I) |
| |
| def parse_structure(astr): |
| """ Return a list of tuples for each function or subroutine each |
| tuple is the start and end of a subroutine or function to be |
| expanded. |
| """ |
| |
| spanlist = [] |
| ind = 0 |
| while True: |
| m = routine_start_re.search(astr, ind) |
| if m is None: |
| break |
| start = m.start() |
| if function_start_re.match(astr, start, m.end()): |
| while True: |
| i = astr.rfind('\n', ind, start) |
| if i==-1: |
| break |
| start = i |
| if astr[i:i+7]!='\n $': |
| break |
| start += 1 |
| m = routine_end_re.search(astr, m.end()) |
| ind = end = m and m.end()-1 or len(astr) |
| spanlist.append((start, end)) |
| return spanlist |
| |
| template_re = re.compile(r"<\s*(\w[\w\d]*)\s*>") |
| named_re = re.compile(r"<\s*(\w[\w\d]*)\s*=\s*(.*?)\s*>") |
| list_re = re.compile(r"<\s*((.*?))\s*>") |
| |
| def find_repl_patterns(astr): |
| reps = named_re.findall(astr) |
| names = {} |
| for rep in reps: |
| name = rep[0].strip() or unique_key(names) |
| repl = rep[1].replace(r'\,', '@comma@') |
| thelist = conv(repl) |
| names[name] = thelist |
| return names |
| |
| def find_and_remove_repl_patterns(astr): |
| names = find_repl_patterns(astr) |
| astr = re.subn(named_re, '', astr)[0] |
| return astr, names |
| |
| item_re = re.compile(r"\A\\(?P<index>\d+)\Z") |
| def conv(astr): |
| b = astr.split(',') |
| l = [x.strip() for x in b] |
| for i in range(len(l)): |
| m = item_re.match(l[i]) |
| if m: |
| j = int(m.group('index')) |
| l[i] = l[j] |
| return ','.join(l) |
| |
| def unique_key(adict): |
| """ Obtain a unique key given a dictionary.""" |
| allkeys = list(adict.keys()) |
| done = False |
| n = 1 |
| while not done: |
| newkey = '__l%s' % (n) |
| if newkey in allkeys: |
| n += 1 |
| else: |
| done = True |
| return newkey |
| |
| |
| template_name_re = re.compile(r'\A\s*(\w[\w\d]*)\s*\Z') |
| def expand_sub(substr, names): |
| substr = substr.replace(r'\>', '@rightarrow@') |
| substr = substr.replace(r'\<', '@leftarrow@') |
| lnames = find_repl_patterns(substr) |
| substr = named_re.sub(r"<\1>", substr) # get rid of definition templates |
| |
| def listrepl(mobj): |
| thelist = conv(mobj.group(1).replace(r'\,', '@comma@')) |
| if template_name_re.match(thelist): |
| return "<%s>" % (thelist) |
| name = None |
| for key in lnames.keys(): # see if list is already in dictionary |
| if lnames[key] == thelist: |
| name = key |
| if name is None: # this list is not in the dictionary yet |
| name = unique_key(lnames) |
| lnames[name] = thelist |
| return "<%s>" % name |
| |
| substr = list_re.sub(listrepl, substr) # convert all lists to named templates |
| # newnames are constructed as needed |
| |
| numsubs = None |
| base_rule = None |
| rules = {} |
| for r in template_re.findall(substr): |
| if r not in rules: |
| thelist = lnames.get(r, names.get(r, None)) |
| if thelist is None: |
| raise ValueError('No replicates found for <%s>' % (r)) |
| if r not in names and not thelist.startswith('_'): |
| names[r] = thelist |
| rule = [i.replace('@comma@', ',') for i in thelist.split(',')] |
| num = len(rule) |
| |
| if numsubs is None: |
| numsubs = num |
| rules[r] = rule |
| base_rule = r |
| elif num == numsubs: |
| rules[r] = rule |
| else: |
| print("Mismatch in number of replacements (base <%s=%s>)" |
| " for <%s=%s>. Ignoring." % |
| (base_rule, ','.join(rules[base_rule]), r, thelist)) |
| if not rules: |
| return substr |
| |
| def namerepl(mobj): |
| name = mobj.group(1) |
| return rules.get(name, (k+1)*[name])[k] |
| |
| newstr = '' |
| for k in range(numsubs): |
| newstr += template_re.sub(namerepl, substr) + '\n\n' |
| |
| newstr = newstr.replace('@rightarrow@', '>') |
| newstr = newstr.replace('@leftarrow@', '<') |
| return newstr |
| |
| def process_str(allstr): |
| newstr = allstr |
| writestr = '' |
| |
| struct = parse_structure(newstr) |
| |
| oldend = 0 |
| names = {} |
| names.update(_special_names) |
| for sub in struct: |
| cleanedstr, defs = find_and_remove_repl_patterns(newstr[oldend:sub[0]]) |
| writestr += cleanedstr |
| names.update(defs) |
| writestr += expand_sub(newstr[sub[0]:sub[1]], names) |
| oldend = sub[1] |
| writestr += newstr[oldend:] |
| |
| return writestr |
| |
| include_src_re = re.compile(r"(\n|\A)\s*include\s*['\"](?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) |
| return process_str(''.join(lines)) |
| |
| _special_names = find_repl_patterns(''' |
| <_c=s,d,c,z> |
| <_t=real,double precision,complex,double complex> |
| <prefix=s,d,c,z> |
| <ftype=real,double precision,complex,double complex> |
| <ctype=float,double,complex_float,complex_double> |
| <ftypereal=real,double precision,\\0,\\1> |
| <ctypereal=float,double,\\0,\\1> |
| ''') |
| |
| 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() |
| writestr = process_str(allstr) |
| outfile.write(writestr) |
| |
| |
| if __name__ == "__main__": |
| main() |