1#!/usr/bin/python
2"""Utility to generate the header files for BOOST_METAPARSE_STRING"""
3
4# Copyright Abel Sinkovics (abel@sinkovics.hu) 2016.
5# Distributed under the Boost Software License, Version 1.0.
6#    (See accompanying file LICENSE_1_0.txt or copy at
7#          http://www.boost.org/LICENSE_1_0.txt)
8
9import argparse
10import math
11import os
12import sys
13
14
15VERSION = 1
16
17
18class Namespace(object):
19    """Generate namespace definition"""
20
21    def __init__(self, out_f, names):
22        self.out_f = out_f
23        self.names = names
24
25    def begin(self):
26        """Generate the beginning part"""
27        self.out_f.write('\n')
28        for depth, name in enumerate(self.names):
29            self.out_f.write(
30                '{0}namespace {1}\n{0}{{\n'.format(self.prefix(depth), name)
31            )
32
33    def end(self):
34        """Generate the closing part"""
35        for depth in xrange(len(self.names) - 1, -1, -1):
36            self.out_f.write('{0}}}\n'.format(self.prefix(depth)))
37
38    def prefix(self, depth=None):
39        """Returns the prefix of a given depth. Returns the prefix code inside
40        the namespace should use when depth is None."""
41        if depth is None:
42            depth = len(self.names)
43        return '  ' * depth
44
45    def __enter__(self):
46        self.begin()
47        return self
48
49    def __exit__(self, typ, value, traceback):
50        self.end()
51
52
53def write_autogen_info(out_f):
54    """Write the comment about the file being autogenerated"""
55    out_f.write(
56        '\n'
57        '// This is an automatically generated header file.\n'
58        '// Generated with the tools/string_headers.py utility of\n'
59        '// Boost.Metaparse\n'
60    )
61
62
63class IncludeGuard(object):
64    """Generate include guards"""
65
66    def __init__(self, out_f):
67        self.out_f = out_f
68
69    def begin(self):
70        """Generate the beginning part"""
71        name = 'BOOST_METAPARSE_V1_CPP11_IMPL_STRING_HPP'
72        self.out_f.write('#ifndef {0}\n#define {0}\n'.format(name))
73        write_autogen_info(self.out_f)
74
75    def end(self):
76        """Generate the closing part"""
77        self.out_f.write('\n#endif\n')
78
79    def __enter__(self):
80        self.begin()
81        return self
82
83    def __exit__(self, typ, value, traceback):
84        self.end()
85
86
87def macro_name(name):
88    """Generate the full macro name"""
89    return 'BOOST_METAPARSE_V{0}_{1}'.format(VERSION, name)
90
91
92def define_macro(out_f, (name, args, body), undefine=False, check=True):
93    """Generate a macro definition or undefinition"""
94    if undefine:
95        out_f.write(
96            '#undef {0}\n'
97            .format(macro_name(name))
98        )
99    else:
100        if args:
101            arg_list = '({0})'.format(', '.join(args))
102        else:
103            arg_list = ''
104
105        if check:
106            out_f.write(
107                '#ifdef {0}\n'
108                '#  error {0} already defined.\n'
109                '#endif\n'
110                .format(macro_name(name))
111            )
112
113        out_f.write(
114            '#define {0}{1} {2}\n'.format(macro_name(name), arg_list, body)
115        )
116
117
118def filename(out_dir, name, undefine=False):
119    """Generate the filename"""
120    if undefine:
121        prefix = 'undef_'
122    else:
123        prefix = ''
124    return os.path.join(out_dir, '{0}{1}.hpp'.format(prefix, name.lower()))
125
126
127def length_limits(max_length_limit, length_limit_step):
128    """Generates the length limits"""
129    string_len = len(str(max_length_limit))
130    return [
131        str(i).zfill(string_len) for i in
132        xrange(
133            length_limit_step,
134            max_length_limit + length_limit_step - 1,
135            length_limit_step
136        )
137    ]
138
139
140def unique_names(count):
141    """Generate count unique variable name"""
142    return ('C{0}'.format(i) for i in xrange(0, count))
143
144
145def generate_take(out_f, steps, line_prefix):
146    """Generate the take function"""
147    out_f.write(
148        '{0}constexpr inline int take(int n_)\n'
149        '{0}{{\n'
150        '{0}  return {1} 0 {2};\n'
151        '{0}}}\n'
152        '\n'.format(
153            line_prefix,
154            ''.join('n_ >= {0} ? {0} : ('.format(s) for s in steps),
155            ')' * len(steps)
156        )
157    )
158
159
160def generate_make_string(out_f, max_step):
161    """Generate the make_string template"""
162    steps = [2 ** n for n in xrange(int(math.log(max_step, 2)), -1, -1)]
163
164    with Namespace(
165        out_f,
166        ['boost', 'metaparse', 'v{0}'.format(VERSION), 'impl']
167    ) as nsp:
168        generate_take(out_f, steps, nsp.prefix())
169
170        out_f.write(
171            '{0}template <int LenNow, int LenRemaining, char... Cs>\n'
172            '{0}struct make_string;\n'
173            '\n'
174            '{0}template <char... Cs>'
175            ' struct make_string<0, 0, Cs...> : string<> {{}};\n'
176            .format(nsp.prefix())
177        )
178
179        disable_sun = False
180        for i in reversed(steps):
181            if i > 64 and not disable_sun:
182                out_f.write('#ifndef __SUNPRO_CC\n')
183                disable_sun = True
184            out_f.write(
185                '{0}template <int LenRemaining,{1}char... Cs>'
186                ' struct make_string<{2},LenRemaining,{3}Cs...> :'
187                ' concat<string<{4}>,'
188                ' typename make_string<take(LenRemaining),'
189                'LenRemaining-take(LenRemaining),Cs...>::type> {{}};\n'
190                .format(
191                    nsp.prefix(),
192                    ''.join('char {0},'.format(n) for n in unique_names(i)),
193                    i,
194                    ''.join('{0},'.format(n) for n in unique_names(i)),
195                    ','.join(unique_names(i))
196                )
197            )
198        if disable_sun:
199            out_f.write('#endif\n')
200
201
202def generate_string(out_dir, limits):
203    """Generate string.hpp"""
204    max_limit = max((int(v) for v in limits))
205
206    with open(filename(out_dir, 'string'), 'wb') as out_f:
207        with IncludeGuard(out_f):
208            out_f.write(
209                '\n'
210                '#include <boost/metaparse/v{0}/cpp11/impl/concat.hpp>\n'
211                '#include <boost/preprocessor/cat.hpp>\n'
212                .format(VERSION)
213            )
214
215            generate_make_string(out_f, 512)
216
217            out_f.write(
218                '\n'
219                '#ifndef BOOST_METAPARSE_LIMIT_STRING_SIZE\n'
220                '#  error BOOST_METAPARSE_LIMIT_STRING_SIZE not defined\n'
221                '#endif\n'
222                '\n'
223                '#if BOOST_METAPARSE_LIMIT_STRING_SIZE > {0}\n'
224                '#  error BOOST_METAPARSE_LIMIT_STRING_SIZE is greater than'
225                ' {0}. To increase the limit run tools/string_headers.py of'
226                ' Boost.Metaparse against your Boost headers.\n'
227                '#endif\n'
228                '\n'
229                .format(max_limit)
230            )
231
232            define_macro(out_f, (
233                'STRING',
234                ['s'],
235                '{0}::make_string< '
236                '{0}::take(sizeof(s)-1), sizeof(s)-1-{0}::take(sizeof(s)-1),'
237                'BOOST_PP_CAT({1}, BOOST_METAPARSE_LIMIT_STRING_SIZE)(s)'
238                '>::type'
239                .format(
240                    '::boost::metaparse::v{0}::impl'.format(VERSION),
241                    macro_name('I')
242                )
243            ))
244
245            out_f.write('\n')
246            for limit in xrange(0, max_limit + 1):
247                out_f.write(
248                    '#define {0} {1}\n'
249                    .format(
250                        macro_name('I{0}'.format(limit)),
251                        macro_name('INDEX_STR{0}'.format(
252                            min(int(l) for l in limits if int(l) >= limit)
253                        ))
254                    )
255                )
256            out_f.write('\n')
257
258            prev_macro = None
259            prev_limit = 0
260            for length_limit in (int(l) for l in limits):
261                this_macro = macro_name('INDEX_STR{0}'.format(length_limit))
262                out_f.write(
263                    '#define {0}(s) {1}{2}\n'
264                    .format(
265                        this_macro,
266                        '{0}(s),'.format(prev_macro) if prev_macro else '',
267                        ','.join(
268                            '{0}((s), {1})'
269                            .format(macro_name('STRING_AT'), i)
270                            for i in xrange(prev_limit, length_limit)
271                        )
272                    )
273                )
274                prev_macro = this_macro
275                prev_limit = length_limit
276
277
278def positive_integer(value):
279    """Throws when the argument is not a positive integer"""
280    val = int(value)
281    if val > 0:
282        return val
283    else:
284        raise argparse.ArgumentTypeError("A positive number is expected")
285
286
287def existing_path(value):
288    """Throws when the path does not exist"""
289    if os.path.exists(value):
290        return value
291    else:
292        raise argparse.ArgumentTypeError("Path {0} not found".format(value))
293
294
295def main():
296    """The main function of the script"""
297    parser = argparse.ArgumentParser(description=__doc__)
298    parser.add_argument(
299        '--boost_dir',
300        required=False,
301        type=existing_path,
302        help='The path to the include/boost directory of Metaparse'
303    )
304    parser.add_argument(
305        '--max_length_limit',
306        required=False,
307        default=2048,
308        type=positive_integer,
309        help='The maximum supported length limit'
310    )
311    parser.add_argument(
312        '--length_limit_step',
313        required=False,
314        default=128,
315        type=positive_integer,
316        help='The longest step at which headers are generated'
317    )
318    args = parser.parse_args()
319
320    if args.boost_dir is None:
321        tools_path = os.path.dirname(os.path.abspath(__file__))
322        boost_dir = os.path.join(
323            os.path.dirname(tools_path),
324            'include',
325            'boost'
326        )
327    else:
328        boost_dir = args.boost_dir
329
330    if args.max_length_limit < 1:
331        sys.stderr.write('Invalid maximum length limit')
332        sys.exit(-1)
333
334    generate_string(
335        os.path.join(
336            boost_dir,
337            'metaparse',
338            'v{0}'.format(VERSION),
339            'cpp11',
340            'impl'
341        ),
342        length_limits(args.max_length_limit, args.length_limit_step)
343    )
344
345
346if __name__ == '__main__':
347    main()
348