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