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