Chris@87
|
1 #!/usr/bin/python
|
Chris@87
|
2 """
|
Chris@87
|
3
|
Chris@87
|
4 process_file(filename)
|
Chris@87
|
5
|
Chris@87
|
6 takes templated file .xxx.src and produces .xxx file where .xxx
|
Chris@87
|
7 is .pyf .f90 or .f using the following template rules:
|
Chris@87
|
8
|
Chris@87
|
9 '<..>' denotes a template.
|
Chris@87
|
10
|
Chris@87
|
11 All function and subroutine blocks in a source file with names that
|
Chris@87
|
12 contain '<..>' will be replicated according to the rules in '<..>'.
|
Chris@87
|
13
|
Chris@87
|
14 The number of comma-separeted words in '<..>' will determine the number of
|
Chris@87
|
15 replicates.
|
Chris@87
|
16
|
Chris@87
|
17 '<..>' may have two different forms, named and short. For example,
|
Chris@87
|
18
|
Chris@87
|
19 named:
|
Chris@87
|
20 <p=d,s,z,c> where anywhere inside a block '<p>' will be replaced with
|
Chris@87
|
21 'd', 's', 'z', and 'c' for each replicate of the block.
|
Chris@87
|
22
|
Chris@87
|
23 <_c> is already defined: <_c=s,d,c,z>
|
Chris@87
|
24 <_t> is already defined: <_t=real,double precision,complex,double complex>
|
Chris@87
|
25
|
Chris@87
|
26 short:
|
Chris@87
|
27 <s,d,c,z>, a short form of the named, useful when no <p> appears inside
|
Chris@87
|
28 a block.
|
Chris@87
|
29
|
Chris@87
|
30 In general, '<..>' contains a comma separated list of arbitrary
|
Chris@87
|
31 expressions. If these expression must contain a comma|leftarrow|rightarrow,
|
Chris@87
|
32 then prepend the comma|leftarrow|rightarrow with a backslash.
|
Chris@87
|
33
|
Chris@87
|
34 If an expression matches '\\<index>' then it will be replaced
|
Chris@87
|
35 by <index>-th expression.
|
Chris@87
|
36
|
Chris@87
|
37 Note that all '<..>' forms in a block must have the same number of
|
Chris@87
|
38 comma-separated entries.
|
Chris@87
|
39
|
Chris@87
|
40 Predefined named template rules:
|
Chris@87
|
41 <prefix=s,d,c,z>
|
Chris@87
|
42 <ftype=real,double precision,complex,double complex>
|
Chris@87
|
43 <ftypereal=real,double precision,\\0,\\1>
|
Chris@87
|
44 <ctype=float,double,complex_float,complex_double>
|
Chris@87
|
45 <ctypereal=float,double,\\0,\\1>
|
Chris@87
|
46
|
Chris@87
|
47 """
|
Chris@87
|
48 from __future__ import division, absolute_import, print_function
|
Chris@87
|
49
|
Chris@87
|
50 __all__ = ['process_str', 'process_file']
|
Chris@87
|
51
|
Chris@87
|
52 import os
|
Chris@87
|
53 import sys
|
Chris@87
|
54 import re
|
Chris@87
|
55
|
Chris@87
|
56 routine_start_re = re.compile(r'(\n|\A)(( (\$|\*))|)\s*(subroutine|function)\b', re.I)
|
Chris@87
|
57 routine_end_re = re.compile(r'\n\s*end\s*(subroutine|function)\b.*(\n|\Z)', re.I)
|
Chris@87
|
58 function_start_re = re.compile(r'\n (\$|\*)\s*function\b', re.I)
|
Chris@87
|
59
|
Chris@87
|
60 def parse_structure(astr):
|
Chris@87
|
61 """ Return a list of tuples for each function or subroutine each
|
Chris@87
|
62 tuple is the start and end of a subroutine or function to be
|
Chris@87
|
63 expanded.
|
Chris@87
|
64 """
|
Chris@87
|
65
|
Chris@87
|
66 spanlist = []
|
Chris@87
|
67 ind = 0
|
Chris@87
|
68 while True:
|
Chris@87
|
69 m = routine_start_re.search(astr, ind)
|
Chris@87
|
70 if m is None:
|
Chris@87
|
71 break
|
Chris@87
|
72 start = m.start()
|
Chris@87
|
73 if function_start_re.match(astr, start, m.end()):
|
Chris@87
|
74 while True:
|
Chris@87
|
75 i = astr.rfind('\n', ind, start)
|
Chris@87
|
76 if i==-1:
|
Chris@87
|
77 break
|
Chris@87
|
78 start = i
|
Chris@87
|
79 if astr[i:i+7]!='\n $':
|
Chris@87
|
80 break
|
Chris@87
|
81 start += 1
|
Chris@87
|
82 m = routine_end_re.search(astr, m.end())
|
Chris@87
|
83 ind = end = m and m.end()-1 or len(astr)
|
Chris@87
|
84 spanlist.append((start, end))
|
Chris@87
|
85 return spanlist
|
Chris@87
|
86
|
Chris@87
|
87 template_re = re.compile(r"<\s*(\w[\w\d]*)\s*>")
|
Chris@87
|
88 named_re = re.compile(r"<\s*(\w[\w\d]*)\s*=\s*(.*?)\s*>")
|
Chris@87
|
89 list_re = re.compile(r"<\s*((.*?))\s*>")
|
Chris@87
|
90
|
Chris@87
|
91 def find_repl_patterns(astr):
|
Chris@87
|
92 reps = named_re.findall(astr)
|
Chris@87
|
93 names = {}
|
Chris@87
|
94 for rep in reps:
|
Chris@87
|
95 name = rep[0].strip() or unique_key(names)
|
Chris@87
|
96 repl = rep[1].replace('\,', '@comma@')
|
Chris@87
|
97 thelist = conv(repl)
|
Chris@87
|
98 names[name] = thelist
|
Chris@87
|
99 return names
|
Chris@87
|
100
|
Chris@87
|
101 item_re = re.compile(r"\A\\(?P<index>\d+)\Z")
|
Chris@87
|
102 def conv(astr):
|
Chris@87
|
103 b = astr.split(',')
|
Chris@87
|
104 l = [x.strip() for x in b]
|
Chris@87
|
105 for i in range(len(l)):
|
Chris@87
|
106 m = item_re.match(l[i])
|
Chris@87
|
107 if m:
|
Chris@87
|
108 j = int(m.group('index'))
|
Chris@87
|
109 l[i] = l[j]
|
Chris@87
|
110 return ','.join(l)
|
Chris@87
|
111
|
Chris@87
|
112 def unique_key(adict):
|
Chris@87
|
113 """ Obtain a unique key given a dictionary."""
|
Chris@87
|
114 allkeys = list(adict.keys())
|
Chris@87
|
115 done = False
|
Chris@87
|
116 n = 1
|
Chris@87
|
117 while not done:
|
Chris@87
|
118 newkey = '__l%s' % (n)
|
Chris@87
|
119 if newkey in allkeys:
|
Chris@87
|
120 n += 1
|
Chris@87
|
121 else:
|
Chris@87
|
122 done = True
|
Chris@87
|
123 return newkey
|
Chris@87
|
124
|
Chris@87
|
125
|
Chris@87
|
126 template_name_re = re.compile(r'\A\s*(\w[\w\d]*)\s*\Z')
|
Chris@87
|
127 def expand_sub(substr, names):
|
Chris@87
|
128 substr = substr.replace('\>', '@rightarrow@')
|
Chris@87
|
129 substr = substr.replace('\<', '@leftarrow@')
|
Chris@87
|
130 lnames = find_repl_patterns(substr)
|
Chris@87
|
131 substr = named_re.sub(r"<\1>", substr) # get rid of definition templates
|
Chris@87
|
132
|
Chris@87
|
133 def listrepl(mobj):
|
Chris@87
|
134 thelist = conv(mobj.group(1).replace('\,', '@comma@'))
|
Chris@87
|
135 if template_name_re.match(thelist):
|
Chris@87
|
136 return "<%s>" % (thelist)
|
Chris@87
|
137 name = None
|
Chris@87
|
138 for key in lnames.keys(): # see if list is already in dictionary
|
Chris@87
|
139 if lnames[key] == thelist:
|
Chris@87
|
140 name = key
|
Chris@87
|
141 if name is None: # this list is not in the dictionary yet
|
Chris@87
|
142 name = unique_key(lnames)
|
Chris@87
|
143 lnames[name] = thelist
|
Chris@87
|
144 return "<%s>" % name
|
Chris@87
|
145
|
Chris@87
|
146 substr = list_re.sub(listrepl, substr) # convert all lists to named templates
|
Chris@87
|
147 # newnames are constructed as needed
|
Chris@87
|
148
|
Chris@87
|
149 numsubs = None
|
Chris@87
|
150 base_rule = None
|
Chris@87
|
151 rules = {}
|
Chris@87
|
152 for r in template_re.findall(substr):
|
Chris@87
|
153 if r not in rules:
|
Chris@87
|
154 thelist = lnames.get(r, names.get(r, None))
|
Chris@87
|
155 if thelist is None:
|
Chris@87
|
156 raise ValueError('No replicates found for <%s>' % (r))
|
Chris@87
|
157 if r not in names and not thelist.startswith('_'):
|
Chris@87
|
158 names[r] = thelist
|
Chris@87
|
159 rule = [i.replace('@comma@', ',') for i in thelist.split(',')]
|
Chris@87
|
160 num = len(rule)
|
Chris@87
|
161
|
Chris@87
|
162 if numsubs is None:
|
Chris@87
|
163 numsubs = num
|
Chris@87
|
164 rules[r] = rule
|
Chris@87
|
165 base_rule = r
|
Chris@87
|
166 elif num == numsubs:
|
Chris@87
|
167 rules[r] = rule
|
Chris@87
|
168 else:
|
Chris@87
|
169 print("Mismatch in number of replacements (base <%s=%s>)"
|
Chris@87
|
170 " for <%s=%s>. Ignoring." %
|
Chris@87
|
171 (base_rule, ','.join(rules[base_rule]), r, thelist))
|
Chris@87
|
172 if not rules:
|
Chris@87
|
173 return substr
|
Chris@87
|
174
|
Chris@87
|
175 def namerepl(mobj):
|
Chris@87
|
176 name = mobj.group(1)
|
Chris@87
|
177 return rules.get(name, (k+1)*[name])[k]
|
Chris@87
|
178
|
Chris@87
|
179 newstr = ''
|
Chris@87
|
180 for k in range(numsubs):
|
Chris@87
|
181 newstr += template_re.sub(namerepl, substr) + '\n\n'
|
Chris@87
|
182
|
Chris@87
|
183 newstr = newstr.replace('@rightarrow@', '>')
|
Chris@87
|
184 newstr = newstr.replace('@leftarrow@', '<')
|
Chris@87
|
185 return newstr
|
Chris@87
|
186
|
Chris@87
|
187 def process_str(allstr):
|
Chris@87
|
188 newstr = allstr
|
Chris@87
|
189 writestr = '' #_head # using _head will break free-format files
|
Chris@87
|
190
|
Chris@87
|
191 struct = parse_structure(newstr)
|
Chris@87
|
192
|
Chris@87
|
193 oldend = 0
|
Chris@87
|
194 names = {}
|
Chris@87
|
195 names.update(_special_names)
|
Chris@87
|
196 for sub in struct:
|
Chris@87
|
197 writestr += newstr[oldend:sub[0]]
|
Chris@87
|
198 names.update(find_repl_patterns(newstr[oldend:sub[0]]))
|
Chris@87
|
199 writestr += expand_sub(newstr[sub[0]:sub[1]], names)
|
Chris@87
|
200 oldend = sub[1]
|
Chris@87
|
201 writestr += newstr[oldend:]
|
Chris@87
|
202
|
Chris@87
|
203 return writestr
|
Chris@87
|
204
|
Chris@87
|
205 include_src_re = re.compile(r"(\n|\A)\s*include\s*['\"](?P<name>[\w\d./\\]+[.]src)['\"]", re.I)
|
Chris@87
|
206
|
Chris@87
|
207 def resolve_includes(source):
|
Chris@87
|
208 d = os.path.dirname(source)
|
Chris@87
|
209 fid = open(source)
|
Chris@87
|
210 lines = []
|
Chris@87
|
211 for line in fid:
|
Chris@87
|
212 m = include_src_re.match(line)
|
Chris@87
|
213 if m:
|
Chris@87
|
214 fn = m.group('name')
|
Chris@87
|
215 if not os.path.isabs(fn):
|
Chris@87
|
216 fn = os.path.join(d, fn)
|
Chris@87
|
217 if os.path.isfile(fn):
|
Chris@87
|
218 print('Including file', fn)
|
Chris@87
|
219 lines.extend(resolve_includes(fn))
|
Chris@87
|
220 else:
|
Chris@87
|
221 lines.append(line)
|
Chris@87
|
222 else:
|
Chris@87
|
223 lines.append(line)
|
Chris@87
|
224 fid.close()
|
Chris@87
|
225 return lines
|
Chris@87
|
226
|
Chris@87
|
227 def process_file(source):
|
Chris@87
|
228 lines = resolve_includes(source)
|
Chris@87
|
229 return process_str(''.join(lines))
|
Chris@87
|
230
|
Chris@87
|
231 _special_names = find_repl_patterns('''
|
Chris@87
|
232 <_c=s,d,c,z>
|
Chris@87
|
233 <_t=real,double precision,complex,double complex>
|
Chris@87
|
234 <prefix=s,d,c,z>
|
Chris@87
|
235 <ftype=real,double precision,complex,double complex>
|
Chris@87
|
236 <ctype=float,double,complex_float,complex_double>
|
Chris@87
|
237 <ftypereal=real,double precision,\\0,\\1>
|
Chris@87
|
238 <ctypereal=float,double,\\0,\\1>
|
Chris@87
|
239 ''')
|
Chris@87
|
240
|
Chris@87
|
241 if __name__ == "__main__":
|
Chris@87
|
242
|
Chris@87
|
243 try:
|
Chris@87
|
244 file = sys.argv[1]
|
Chris@87
|
245 except IndexError:
|
Chris@87
|
246 fid = sys.stdin
|
Chris@87
|
247 outfile = sys.stdout
|
Chris@87
|
248 else:
|
Chris@87
|
249 fid = open(file, 'r')
|
Chris@87
|
250 (base, ext) = os.path.splitext(file)
|
Chris@87
|
251 newname = base
|
Chris@87
|
252 outfile = open(newname, 'w')
|
Chris@87
|
253
|
Chris@87
|
254 allstr = fid.read()
|
Chris@87
|
255 writestr = process_str(allstr)
|
Chris@87
|
256 outfile.write(writestr)
|