Skip to content

Commit 1bbde28

Browse files
Merge pull request #77 from deinst/parserst
Parserst
2 parents 82ede3a + 476b572 commit 1bbde28

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed

bin/rst_to_pxd.py

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#!/usr/bin/env python3
2+
3+
from collections import defaultdict
4+
import re
5+
import sys
6+
import os
7+
import fnmatch
8+
import argparse
9+
10+
"""
11+
This is relatively rudimentary, but works for the purpose intended
12+
13+
To use this to parse an arb rst file
14+
15+
python rst_to_pxd basename
16+
17+
or to parse a flint rst file
18+
19+
python rst_to_pxd flint/basename
20+
21+
the pxd file is dumped to stdout
22+
23+
for example
24+
python rst_to_pxd flint/fmpz_poly
25+
26+
will output a skeleton fmpz_poly.pxd to stdout
27+
28+
29+
You will need to configure the location of the flintlib submodule directory and the locations of the flint and
30+
arb doc source directories.
31+
32+
The other useful configuration is the comment_types and set which are two representations of the types that are
33+
not implemented and therefore we want to comment functions that reference them
34+
35+
TODO: DRY with comment types, also should be able to add commented types from the command line.
36+
TODO: don't import self
37+
38+
"""
39+
40+
# recognize a function definition in rst
41+
is_func = re.compile(r"\.\.( )+(c:)?function( )*::")
42+
# rename types to avoid python -- c name collisions
43+
rename_types = [(re.compile(r"\bfmpz\b"),"fmpz_struct"),(re.compile(r"\bfmpq\b"), "fmpq_struct")]
44+
# comment out functions which use these types
45+
comment_types = re.compile(r"(\bFILE\b)|(\bmpz_t\b)|(\bmpq_t\b)")
46+
comment_set = set(["FILE", "mpz_t", "mpq_t"])
47+
c_types = set(["char", "short", "long", "int", "float", "double"])
48+
type_modifers = re.compile(r"\*|(\bconst\b)|(\bunsigned\b)|(\bsigned\b)")
49+
import_dict = {}
50+
51+
def get_cython_struct_types(file):
52+
"""
53+
Extract cython types from a pxd file.
54+
"""
55+
ret = []
56+
for line in file:
57+
l = line.strip()
58+
if l[:8] == "ctypedef":
59+
if l[-1] == ']':
60+
l = l[:l.rfind('[')]
61+
else:
62+
l = l.strip(':')
63+
ret.append(l.split()[-1])
64+
return ret
65+
66+
def fill_import_dict(pyflintlibdir):
67+
"""
68+
Get a map from cython structs to the pxd that defines them
69+
"""
70+
with os.scandir(pyflintlibdir) as entry:
71+
for f in entry:
72+
if fnmatch.fnmatch(f.name, "*.pxd"):
73+
with open(f.path) as pxd:
74+
for t in get_cython_struct_types(pxd):
75+
import_dict[t] = f.name.split('.')[0]
76+
77+
def undecorate(str):
78+
"""
79+
remove variable name, const, *, etc. to just get types
80+
"""
81+
ret = str.strip()
82+
ret = ret[:ret.rfind(' ')]
83+
ret = re.sub(type_modifers, '', ret)
84+
return ret.strip()
85+
86+
def get_parameter_types(str):
87+
params = str[str.find("(") + 1 : str.rfind(")")].split(",")
88+
return [undecorate(s) for s in params]
89+
90+
def clean_types(function):
91+
ret = function.strip()
92+
for old, new in rename_types:
93+
ret = re.sub(old, new, ret)
94+
return ret
95+
96+
def get_functions(file):
97+
"""
98+
Get a list of functions from an rst file
99+
"""
100+
ret = []
101+
in_list = False
102+
for line in file:
103+
m = is_func.match(line)
104+
if m:
105+
ret.append( clean_types(line[m.end():]))
106+
in_list = True
107+
else:
108+
if in_list:
109+
if line.strip() == '':
110+
in_list = False
111+
else:
112+
ret.append(clean_types(line))
113+
return ret
114+
115+
def get_all_types(function_list):
116+
ret = set()
117+
for f in function_list:
118+
for t in get_parameter_types(f):
119+
ret.add(t)
120+
return ret
121+
122+
def gen_imports(function_list):
123+
"""
124+
Generate import statements for known functions.
125+
"""
126+
imports = defaultdict(list)
127+
s = get_all_types(function_list)
128+
s = s - c_types
129+
ret = set([])
130+
for t in s:
131+
if t in import_dict:
132+
imports[import_dict[t]].append(t)
133+
else:
134+
ret.add(t)
135+
for k,v in imports.items():
136+
types = ", ".join(v)
137+
print("from flint.flintlib." + k + " cimport " + types)
138+
return ret
139+
140+
def generate_pxd_file(h_name, opts):
141+
fill_import_dict(opts.flint_lib_dir)
142+
l=[]
143+
docdir = opts.arb_doc_dir
144+
name = h_name
145+
if name[:6] == "flint/":
146+
docdir = opts.flint_doc_dir
147+
name = name[6:]
148+
with open(os.path.join(docdir, name + ".rst")) as f:
149+
l = get_functions(f)
150+
s = gen_imports(l)
151+
print()
152+
print ("\n# unimported types ", s - comment_set)
153+
print()
154+
print(r'cdef extern from "' + h_name +r'.h":')
155+
for f in l:
156+
if comment_types.search(f):
157+
print(" # " + f)
158+
else:
159+
print(" " + f)
160+
161+
162+
def main(*args):
163+
usage = """
164+
$ cd /path/to/python-flint
165+
$ bin/rst_to_pxd.py flint/fmpz --flint-doc-dir=/path/to/flint/doc/source
166+
"""
167+
parser = argparse.ArgumentParser(description='Generate a pxd file from an rst file',
168+
usage=usage)
169+
parser.add_argument('--flint-lib-dir', help='location of the flintlib submodule',
170+
default="./src/flint/flintlib")
171+
parser.add_argument('--arb-doc-dir', help='location of the arb doc source directory',
172+
default="/Users/davideinstein/projects/arb/doc/source")
173+
parser.add_argument('--flint-doc-dir', help='location of the flint doc source directory',
174+
default="/Users/davideinstein/projects/flint2/doc/source")
175+
parser.add_argument('name', help='name of the rst file to parse (e.g. flint/fmpz)')
176+
177+
args = parser.parse_args()
178+
179+
generate_pxd_file(args.name, args)
180+
181+
182+
if __name__ == "__main__":
183+
main(*sys.argv[1:])
184+

0 commit comments

Comments
 (0)