Skip to content

Commit 94606a2

Browse files
Merge pull request #90 from IntelPython/vendor-conv-template
Vendor conv_template.py script from numpy.distutils
2 parents abe28a3 + 9259041 commit 94606a2

File tree

4 files changed

+336
-1
lines changed

4 files changed

+336
-1
lines changed

_vendored/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Vendored files
2+
3+
File `conv_template.py` is copied from NumPy's numpy/distutils folder, since
4+
`numpy.distutils` is absent from the installation layout starting with
5+
Python 3.12

_vendored/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# empty file

_vendored/conv_template.py

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
#!/usr/bin/env python3
2+
"""
3+
takes templated file .xxx.src and produces .xxx file where .xxx is
4+
.i or .c or .h, using the following template rules
5+
6+
/**begin repeat -- on a line by itself marks the start of a repeated code
7+
segment
8+
/**end repeat**/ -- on a line by itself marks it's end
9+
10+
After the /**begin repeat and before the */, all the named templates are placed
11+
these should all have the same number of replacements
12+
13+
Repeat blocks can be nested, with each nested block labeled with its depth,
14+
i.e.
15+
/**begin repeat1
16+
*....
17+
*/
18+
/**end repeat1**/
19+
20+
When using nested loops, you can optionally exclude particular
21+
combinations of the variables using (inside the comment portion of the inner loop):
22+
23+
:exclude: var1=value1, var2=value2, ...
24+
25+
This will exclude the pattern where var1 is value1 and var2 is value2 when
26+
the result is being generated.
27+
28+
29+
In the main body each replace will use one entry from the list of named replacements
30+
31+
Note that all #..# forms in a block must have the same number of
32+
comma-separated entries.
33+
34+
Example:
35+
36+
An input file containing
37+
38+
/**begin repeat
39+
* #a = 1,2,3#
40+
* #b = 1,2,3#
41+
*/
42+
43+
/**begin repeat1
44+
* #c = ted, jim#
45+
*/
46+
@a@, @b@, @c@
47+
/**end repeat1**/
48+
49+
/**end repeat**/
50+
51+
produces
52+
53+
line 1 "template.c.src"
54+
55+
/*
56+
*********************************************************************
57+
** This file was autogenerated from a template DO NOT EDIT!!**
58+
** Changes should be made to the original source (.src) file **
59+
*********************************************************************
60+
*/
61+
62+
#line 9
63+
1, 1, ted
64+
65+
#line 9
66+
1, 1, jim
67+
68+
#line 9
69+
2, 2, ted
70+
71+
#line 9
72+
2, 2, jim
73+
74+
#line 9
75+
3, 3, ted
76+
77+
#line 9
78+
3, 3, jim
79+
80+
"""
81+
82+
__all__ = ['process_str', 'process_file']
83+
84+
import os
85+
import sys
86+
import re
87+
88+
# names for replacement that are already global.
89+
global_names = {}
90+
91+
# header placed at the front of head processed file
92+
header =\
93+
"""
94+
/*
95+
*****************************************************************************
96+
** This file was autogenerated from a template DO NOT EDIT!!!! **
97+
** Changes should be made to the original source (.src) file **
98+
*****************************************************************************
99+
*/
100+
101+
"""
102+
# Parse string for repeat loops
103+
def parse_structure(astr, level):
104+
"""
105+
The returned line number is from the beginning of the string, starting
106+
at zero. Returns an empty list if no loops found.
107+
108+
"""
109+
if level == 0 :
110+
loopbeg = "/**begin repeat"
111+
loopend = "/**end repeat**/"
112+
else :
113+
loopbeg = "/**begin repeat%d" % level
114+
loopend = "/**end repeat%d**/" % level
115+
116+
ind = 0
117+
line = 0
118+
spanlist = []
119+
while True:
120+
start = astr.find(loopbeg, ind)
121+
if start == -1:
122+
break
123+
start2 = astr.find("*/", start)
124+
start2 = astr.find("\n", start2)
125+
fini1 = astr.find(loopend, start2)
126+
fini2 = astr.find("\n", fini1)
127+
line += astr.count("\n", ind, start2+1)
128+
spanlist.append((start, start2+1, fini1, fini2+1, line))
129+
line += astr.count("\n", start2+1, fini2)
130+
ind = fini2
131+
spanlist.sort()
132+
return spanlist
133+
134+
135+
def paren_repl(obj):
136+
torep = obj.group(1)
137+
numrep = obj.group(2)
138+
return ','.join([torep]*int(numrep))
139+
140+
parenrep = re.compile(r"\(([^)]*)\)\*(\d+)")
141+
plainrep = re.compile(r"([^*]+)\*(\d+)")
142+
def parse_values(astr):
143+
# replaces all occurrences of '(a,b,c)*4' in astr
144+
# with 'a,b,c,a,b,c,a,b,c,a,b,c'. Empty braces generate
145+
# empty values, i.e., ()*4 yields ',,,'. The result is
146+
# split at ',' and a list of values returned.
147+
astr = parenrep.sub(paren_repl, astr)
148+
# replaces occurrences of xxx*3 with xxx, xxx, xxx
149+
astr = ','.join([plainrep.sub(paren_repl, x.strip())
150+
for x in astr.split(',')])
151+
return astr.split(',')
152+
153+
154+
stripast = re.compile(r"\n\s*\*?")
155+
named_re = re.compile(r"#\s*(\w*)\s*=([^#]*)#")
156+
exclude_vars_re = re.compile(r"(\w*)=(\w*)")
157+
exclude_re = re.compile(":exclude:")
158+
def parse_loop_header(loophead) :
159+
"""Find all named replacements in the header
160+
161+
Returns a list of dictionaries, one for each loop iteration,
162+
where each key is a name to be substituted and the corresponding
163+
value is the replacement string.
164+
165+
Also return a list of exclusions. The exclusions are dictionaries
166+
of key value pairs. There can be more than one exclusion.
167+
[{'var1':'value1', 'var2', 'value2'[,...]}, ...]
168+
169+
"""
170+
# Strip out '\n' and leading '*', if any, in continuation lines.
171+
# This should not effect code previous to this change as
172+
# continuation lines were not allowed.
173+
loophead = stripast.sub("", loophead)
174+
# parse out the names and lists of values
175+
names = []
176+
reps = named_re.findall(loophead)
177+
nsub = None
178+
for rep in reps:
179+
name = rep[0]
180+
vals = parse_values(rep[1])
181+
size = len(vals)
182+
if nsub is None :
183+
nsub = size
184+
elif nsub != size :
185+
msg = "Mismatch in number of values, %d != %d\n%s = %s"
186+
raise ValueError(msg % (nsub, size, name, vals))
187+
names.append((name, vals))
188+
189+
190+
# Find any exclude variables
191+
excludes = []
192+
193+
for obj in exclude_re.finditer(loophead):
194+
span = obj.span()
195+
# find next newline
196+
endline = loophead.find('\n', span[1])
197+
substr = loophead[span[1]:endline]
198+
ex_names = exclude_vars_re.findall(substr)
199+
excludes.append(dict(ex_names))
200+
201+
# generate list of dictionaries, one for each template iteration
202+
dlist = []
203+
if nsub is None :
204+
raise ValueError("No substitution variables found")
205+
for i in range(nsub):
206+
tmp = {name: vals[i] for name, vals in names}
207+
dlist.append(tmp)
208+
return dlist
209+
210+
replace_re = re.compile(r"@(\w+)@")
211+
def parse_string(astr, env, level, line) :
212+
lineno = "#line %d\n" % line
213+
214+
# local function for string replacement, uses env
215+
def replace(match):
216+
name = match.group(1)
217+
try :
218+
val = env[name]
219+
except KeyError:
220+
msg = 'line %d: no definition of key "%s"'%(line, name)
221+
raise ValueError(msg) from None
222+
return val
223+
224+
code = [lineno]
225+
struct = parse_structure(astr, level)
226+
if struct :
227+
# recurse over inner loops
228+
oldend = 0
229+
newlevel = level + 1
230+
for sub in struct:
231+
pref = astr[oldend:sub[0]]
232+
head = astr[sub[0]:sub[1]]
233+
text = astr[sub[1]:sub[2]]
234+
oldend = sub[3]
235+
newline = line + sub[4]
236+
code.append(replace_re.sub(replace, pref))
237+
try :
238+
envlist = parse_loop_header(head)
239+
except ValueError as e:
240+
msg = "line %d: %s" % (newline, e)
241+
raise ValueError(msg)
242+
for newenv in envlist :
243+
newenv.update(env)
244+
newcode = parse_string(text, newenv, newlevel, newline)
245+
code.extend(newcode)
246+
suff = astr[oldend:]
247+
code.append(replace_re.sub(replace, suff))
248+
else :
249+
# replace keys
250+
code.append(replace_re.sub(replace, astr))
251+
code.append('\n')
252+
return ''.join(code)
253+
254+
def process_str(astr):
255+
code = [header]
256+
code.extend(parse_string(astr, global_names, 0, 1))
257+
return ''.join(code)
258+
259+
260+
include_src_re = re.compile(r"(\n|\A)#include\s*['\"]"
261+
r"(?P<name>[\w\d./\\]+[.]src)['\"]", re.I)
262+
263+
def resolve_includes(source):
264+
d = os.path.dirname(source)
265+
with open(source) as fid:
266+
lines = []
267+
for line in fid:
268+
m = include_src_re.match(line)
269+
if m:
270+
fn = m.group('name')
271+
if not os.path.isabs(fn):
272+
fn = os.path.join(d, fn)
273+
if os.path.isfile(fn):
274+
lines.extend(resolve_includes(fn))
275+
else:
276+
lines.append(line)
277+
else:
278+
lines.append(line)
279+
return lines
280+
281+
def process_file(source):
282+
lines = resolve_includes(source)
283+
sourcefile = os.path.normcase(source).replace("\\", "\\\\")
284+
try:
285+
code = process_str(''.join(lines))
286+
except ValueError as e:
287+
raise ValueError('In "%s" loop at %s' % (sourcefile, e)) from None
288+
return '#line 1 "%s"\n%s' % (sourcefile, code)
289+
290+
291+
def unique_key(adict):
292+
# this obtains a unique key given a dictionary
293+
# currently it works by appending together n of the letters of the
294+
# current keys and increasing n until a unique key is found
295+
# -- not particularly quick
296+
allkeys = list(adict.keys())
297+
done = False
298+
n = 1
299+
while not done:
300+
newkey = "".join([x[:n] for x in allkeys])
301+
if newkey in allkeys:
302+
n += 1
303+
else:
304+
done = True
305+
return newkey
306+
307+
308+
def main():
309+
try:
310+
file = sys.argv[1]
311+
except IndexError:
312+
fid = sys.stdin
313+
outfile = sys.stdout
314+
else:
315+
fid = open(file, 'r')
316+
(base, ext) = os.path.splitext(file)
317+
newname = base
318+
outfile = open(newname, 'w')
319+
320+
allstr = fid.read()
321+
try:
322+
writestr = process_str(allstr)
323+
except ValueError as e:
324+
raise ValueError("In %s loop at %s" % (file, e)) from None
325+
326+
outfile.write(writestr)
327+
328+
if __name__ == "__main__":
329+
main()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import Cython.Build
3232
from setuptools import setup, Extension
3333
import numpy as np
34-
from numpy.distutils.conv_template import process_file as process_c_file
34+
from _vendored.conv_template import process_file as process_c_file
3535

3636
with io.open('mkl_fft/_version.py', 'rt', encoding='utf8') as f:
3737
version = re.search(r'__version__ = \'(.*?)\'', f.read()).group(1)

0 commit comments

Comments
 (0)