Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sage
Path: blob/develop/src/sage_setup/autogen/flint/reader.py
4086 views
1
r"""
2
Extraction of function, macros, types from flint documentation.
3
"""
4
5
#*****************************************************************************
6
# Copyright (C) 2023 Vincent Delecroix <[email protected]>
7
#
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 2 of the License, or
11
# (at your option) any later version.
12
# http://www.gnu.org/licenses/
13
#*****************************************************************************
14
15
import os
16
from .env import FLINT_INCLUDE_DIR, FLINT_DOC_DIR
17
18
19
class Extractor:
20
r"""
21
Tool to extract function declarations from a flint .rst file
22
"""
23
NONE = 0
24
DOC = 1
25
FUNCTION_DECLARATION = 2
26
MACRO_DECLARATION = 4
27
TYPE_DECLARATION = 8
28
29
def __init__(self, filename):
30
self.filename = filename
31
if not filename.endswith('.rst'):
32
raise ValueError
33
34
# Attributes that are modified throughout the document parsing
35
self.state = self.NONE # position in the documentation
36
self.section = None # current section
37
self.content = {} # section -> list of pairs (function signatures, func documentation)
38
self.functions = [] # current list of pairs (function signatures, func documentation)
39
self.signatures = [] # current list of function/macro/type signatures
40
self.doc = [] # current function documentation
41
42
with open(filename) as f:
43
text = f.read()
44
self.lines = text.splitlines()
45
self.i = 0
46
47
def run(self):
48
while self.process_line():
49
pass
50
if self.state & self.FUNCTION_DECLARATION:
51
self.add_function()
52
if self.state & self.MACRO_DECLARATION:
53
self.add_macro()
54
if self.functions:
55
self.update_section()
56
self.state = self.NONE
57
58
def update_section(self):
59
if self.section not in self.content:
60
self.content[self.section] = []
61
self.content[self.section] += tuple(self.functions)
62
self.functions.clear()
63
64
def clean_doc(self):
65
# Remove empty lines at the end of documentation
66
while self.doc and not self.doc[-1]:
67
self.doc.pop()
68
69
for i, line in enumerate(self.doc):
70
# To make sage linter happier
71
line = line.replace('\\choose ', 'choose ')
72
self.doc[i] = line
73
74
@staticmethod
75
def has_boolean_return_type(func_signature):
76
r"""
77
Determine whether the function func_signature has a boolean return type.
78
79
If so, it will be declared in Cython as `bint` rather than `int`.
80
"""
81
if func_signature.count('(') != 1 or func_signature.count(')') != 1:
82
return False
83
84
j = func_signature.index('(')
85
func_name = func_signature[:j].strip().split()
86
if len(func_name) != 2:
87
return False
88
89
return_type = func_name[0]
90
if return_type != 'int':
91
return False
92
93
func_name = func_name[1]
94
95
return func_name.startswith('is_') or \
96
'_is_' in func_name or \
97
func_name.endswith('_eq') or \
98
func_name.endswith('_ne') or \
99
func_name.endswith('_lt') or \
100
func_name.endswith('_le') or \
101
func_name.endswith('_gt') or \
102
func_name.endswith('_ge') or \
103
'_contains_' in func_name or \
104
func_name.endswith('_contains') or \
105
'_equal_' in func_name or \
106
func_name.endswith('_equal') or \
107
func_name.endswith('_overlaps')
108
109
def clean_signatures(self):
110
if (self.state & self.FUNCTION_DECLARATION) or (self.state & self.MACRO_DECLARATION):
111
for i, func_signature in enumerate(self.signatures):
112
replacement = [('(void)', '()'), (' enum ', ' ')]
113
for bad_type, good_type in replacement:
114
func_signature = func_signature.replace(bad_type, good_type)
115
116
bad_arg_names = [('in', 'input'), ('lambda', 'lmbda'), ('iter', 'it'), ('is', 'iis')]
117
replacements = [(pattern.format(bad), pattern.format(good)) for pattern in [' {},', ' {})', '*{},', '*{})'] for bad, good in bad_arg_names]
118
for bad_form, good_form in replacements:
119
func_signature = func_signature.replace(bad_form, good_form)
120
121
if self.has_boolean_return_type(func_signature):
122
func_signature = func_signature.strip()
123
if not func_signature.startswith('int '):
124
raise RuntimeError
125
func_signature = 'b' + func_signature
126
127
self.signatures[i] = func_signature
128
129
def add_declaration(self):
130
if self.state & self.FUNCTION_DECLARATION:
131
self.add_function()
132
elif self.state & self.MACRO_DECLARATION:
133
self.add_macro()
134
elif self.state & self.TYPE_DECLARATION:
135
self.add_type()
136
137
self.signatures.clear()
138
self.doc.clear()
139
self.state = self.NONE
140
141
def add_function(self):
142
self.clean_doc()
143
144
# Drop va_list argument
145
signatures = []
146
for func_signature in self.signatures:
147
if '(' not in func_signature or ')' not in func_signature:
148
raise RuntimeError(func_signature)
149
elif 'va_list ' in func_signature:
150
print('Warning: va_list unsupported {}'.format(func_signature))
151
else:
152
signatures.append(func_signature)
153
self.signatures = signatures
154
self.clean_signatures()
155
156
self.functions.append((tuple(self.signatures), tuple(self.doc)))
157
158
def add_macro(self):
159
# TODO: we might want to support auto-generation of macros
160
return
161
162
def add_type(self):
163
# TODO: we might want to support auto-generation of types
164
return
165
166
def process_line(self):
167
r"""
168
Process one line of documentation.
169
"""
170
if self.i >= len(self.lines):
171
return 0
172
173
if bool(self.state & self.FUNCTION_DECLARATION) + bool(self.state & self.MACRO_DECLARATION) + bool(self.state & self.TYPE_DECLARATION) > 1:
174
raise RuntimeError('self.state = {} and i = {}'.format(self.state, self.i))
175
176
line = self.lines[self.i]
177
if line.startswith('.. function::'):
178
self.add_declaration()
179
line_rest = line.removeprefix('.. function::')
180
if not line_rest.startswith(' '):
181
print('Warning: no space {}'.format(line))
182
self.state = self.FUNCTION_DECLARATION
183
self.i += 1
184
signature = line_rest.strip()
185
while signature.endswith('\\'):
186
signature = signature.removesuffix('\\').strip() + ' ' + self.lines[self.i].strip()
187
self.i += 1
188
self.signatures.append(signature)
189
elif line.startswith('.. macro::'):
190
self.add_declaration()
191
if line[10] != ' ':
192
print('Warning no space{}'.format(line))
193
self.signatures.append(line[10:].strip())
194
self.state = self.MACRO_DECLARATION
195
self.i += 1
196
elif line.startswith('.. type::'):
197
# type
198
# NOTE: we do nothing as the documentation duplicates type declaration
199
# and lacks the actual list of attributes
200
self.add_declaration()
201
self.state = self.TYPE_DECLARATION
202
self.i += 1
203
elif self.state == self.FUNCTION_DECLARATION:
204
if len(line) > 14 and line.startswith(' ' * 14):
205
# function with similar declaration
206
line = line[14:].strip()
207
if line:
208
self.signatures.append(line)
209
self.i += 1
210
elif not line.strip():
211
# leaving function declaration
212
self.state |= self.DOC
213
self.i += 1
214
else:
215
raise ValueError(line)
216
elif self.state == self.MACRO_DECLARATION:
217
if len(line) > 10 and line.startswith(' ' * 10):
218
# macro with similar declaration
219
line = line[10:].strip()
220
if line:
221
self.signatures.append(line)
222
self.i += 1
223
elif not line.strip():
224
# leaving macro declaration
225
self.state |= self.DOC
226
self.i += 1
227
else:
228
raise ValueError(line)
229
elif (self.state & self.DOC) and line.startswith(' '):
230
# function doc
231
line = line.strip()
232
if line:
233
self.doc.append(line)
234
self.i += 1
235
elif self.i + 1 < len(self.lines) and self.lines[self.i + 1].startswith('----'):
236
# new section
237
self.add_declaration()
238
if self.functions:
239
self.update_section()
240
section = line
241
self.i += 2
242
elif not line:
243
self.i += 1
244
else:
245
self.add_declaration()
246
self.i += 1
247
248
return 1
249
250
251
def extract_functions(filename):
252
r"""
253
OUTPUT:
254
255
dictionary: section -> list of pairs (func_sig, doc)
256
"""
257
e = Extractor(filename)
258
e.run()
259
return e.content
260
261