summaryrefslogtreecommitdiffstats
path: root/web/server/h2o/libh2o/deps/picotls/deps/cifra/doc/build.py
blob: 0b0068252ad4d49191ae02261fed6b6857d6e85a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
"""
Extracts documentation from cifra headers.

We want to embed documentation in headers for good
locality.  But want to write rst for formatting by sphinx.
This is a problem.

'Breathe' provides a bridge between doxygen and sphinx,
but doxygen's documentation markup is pretty awful.

Therefore, we write rst directly in C headers, and then
extract it here.  The rules are: a C block comment
starting exactly '/* .. ' is dedented, and then included
verbatim.  The convention is that the documentation
preceeds the C declarations they apply to, and that struct
members are documented before the struct they are contained
within. eg:

/* .. c:function:: int foo(int bar)
 * Foos a bar, returning the foo coefficient.
 */
int foo(int bar);

/* .. c:type:: thing
 * Container for things.
 *
 * .. c:member:: int thing.foo
 * Count of foos.
 * 
 * .. c:member:: int thing.bar
 * Count of bars.
 */
typedef struct
{
  int foo;
  int bar;
} thing;

As a special effect, the following tokens are replaced
in the comments:

 - $DECL: the immediately following function declaration
"""

import glob
import re
import StringIO

# We know which headers constitute the external interface.
EXTERNAL = """
aes
cf_config
chash
hmac
modes
pbkdf2
prp
salsa20
sha1
sha2
sha3
norx
poly1305
chacha20poly1305
drbg
""".split()

# Basic idea of a C identifier
C_IDENTIFIER = '[a-zA-Z_][a-zA-Z0-9_]+'

DECL_RE = re.compile(r'^\s*(.+?' + C_IDENTIFIER + '\(.+?\));', re.MULTILINE | re.DOTALL)
COMMENT_RE = re.compile(r'^\s*\/\* (\.\..*?) \*\/$', re.MULTILINE | re.DOTALL)
INTRO_RE = re.compile(r'^\s*\/\*\*(.*?)\*\/$', re.MULTILINE | re.DOTALL)
NEW_SECTION_RE = re.compile(r'^..+\n(==+|--+)$', re.MULTILINE)

class section(object):
    def __init__(self):
        self.intro = []
        self.macros = []
        self.types = []
        self.functions = []
        self.values = []

    def __repr__(self):
        return repr(self.format()[:30])
    
    def __str__(self):
        return repr(self.format()[:30])

    def format(self):
        f = StringIO.StringIO()

        def emit(title, section):
            if len(section) == 0:
                return
           
            if title:
                print >>f, title
                print >>f, '*' * len(title)
                print >>f

            for s in section:
                print >>f, s.getvalue()
        
        emit(None, self.intro)
        emit('Macros', self.macros)
        emit('Types', self.types)
        emit('Functions', self.functions)
        emit('Values', self.values)

        return f.getvalue()

    def is_empty(self):
        items = len(self.intro) + len(self.macros) + len(self.types) + \
                len(self.functions) + len(self.values)
        return 0 == items
    
    def add_item(self, sec):
        f = StringIO.StringIO()
        sec.append(f)
        return f

def massage_decl(decl):
    """
    Tart-up a C function declaration: remove storage qualifiers,
    smush onto one line, escape asterisks.
    """
    for storage in 'extern static inline'.split():
        decl = decl.replace(storage + ' ', '')
    
    fixed_lines = ' '.join(line.strip() for line in decl.splitlines())

    return fixed_lines.replace('*', '\\*')

def replace_decl(comment, comment_match, header):
    if '$DECL' not in comment:
        return comment

    start = comment_match.end(0) + 1
    decl_match = DECL_RE.match(header, start)

    if decl_match is None:
        print 'Cause:', comment
        print 'Trailer:', header[start:start+60]
        raise IOError('$DECL present but cannot find following DECL')

    decl = decl_match.group(1)
    decl = massage_decl(decl)
    return comment.replace('$DECL', decl)

def decomment(lines):
    for i in range(len(lines)):
        if lines[i].startswith(' *'):
            lines[i] = lines[i][3:]
        lines[i] = lines[i].strip()

def drop_empty_prefix(lines):
    while len(lines) and lines[0] == '':
        lines.pop(0)

def starts_new_section(lines):
    txt = '\n'.join(lines)
    r = NEW_SECTION_RE.search(txt) is not None
    return r

def process(header, rst):
    """
    Converts a header into restructured text.

    header is a file-like opened to read the header.
    rst is a file-like opened to write the rst results.
    """

    hh = header.read()

    # Collect definitions into sections
    sec = None
    all_sections = []
    intro, macros, types, functions, values = [], [], [], [], []

    def add_section():
        if sec and not sec.is_empty():
            all_sections.append(sec)
        return section()

    sec = add_section()

    offs = 0

    while True:
        intro_match = INTRO_RE.search(hh, offs)
        comment_match = COMMENT_RE.search(hh, offs)

        if intro_match is None and comment_match is None:
            break

        # process earliest occuring
        if intro_match is not None and (comment_match is None or intro_match.start(0) < comment_match.start(0)):
            txt = intro_match.group(1)
            
            lines = txt.splitlines()
            decomment(lines)
            drop_empty_prefix(lines)

            if starts_new_section(lines):
                sec = add_section()

            outf = sec.add_item(sec.intro)

            for l in lines:
                print >>outf, l
            offs = intro_match.end(0) + 1
            continue

        if comment_match is not None and (intro_match is None or comment_match.start(0) < intro_match.start(0)):
            txt = comment_match.group(1)

            # work out which section this goes into
            outf = None
            if '.. c:macro::' in txt:
                outf = sec.add_item(sec.macros)
            elif '.. c:type::' in txt:
                outf = sec.add_item(sec.types)
            elif '.. c:var::' in txt:
                outf = sec.add_item(sec.values)
            elif '.. c:function::' in txt:
                outf = sec.add_item(sec.functions)
            elif '.. c:member::' in txt:
                if len(types) == 0:
                    raise IOError('c:member must come after a c:type')
                outf = types[-1]
            else:
                raise IOError('Cannot categorise item: ' + txt)

            # expand $DECL
            txt = replace_decl(txt, comment_match, hh)
            
            # decomment lines
            lines = txt.splitlines()
            decomment(lines)

            # domain lines are unindented
            while lines and lines[0].startswith('.. '):
                print >>outf, lines.pop(0)
            print >>outf

            # empty prefix lines are stripped
            drop_empty_prefix(lines)

            # other lines are indented
            for line in lines:
                if len(line):
                    line = '  ' + line
                print >>outf, line
            
            offs = comment_match.end(0) + 1
            continue

    add_section()

    for sec in all_sections:
        rst.write(sec.format())

def run():
    for fn in EXTERNAL:
        print '** build', fn
        header = '../src/' + fn + '.h'
        rst = fn + '.rst'
        with open(header, 'r') as fh:
            with open(rst, 'w') as fr:
                process(fh, fr)

if __name__ == '__main__':
    run()