#!/usr/bin/python """Utility to generate the header files for BOOST_METAPARSE_STRING""" # Copyright Abel Sinkovics (abel@sinkovics.hu) 2016. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) import argparse import math import os import sys VERSION = 1 class Namespace(object): """Generate namespace definition""" def __init__(self, out_f, names): self.out_f = out_f self.names = names def begin(self): """Generate the beginning part""" self.out_f.write('\n') for depth, name in enumerate(self.names): self.out_f.write( '{0}namespace {1}\n{0}{{\n'.format(self.prefix(depth), name) ) def end(self): """Generate the closing part""" for depth in xrange(len(self.names) - 1, -1, -1): self.out_f.write('{0}}}\n'.format(self.prefix(depth))) def prefix(self, depth=None): """Returns the prefix of a given depth. Returns the prefix code inside the namespace should use when depth is None.""" if depth is None: depth = len(self.names) return ' ' * depth def __enter__(self): self.begin() return self def __exit__(self, typ, value, traceback): self.end() def write_autogen_info(out_f): """Write the comment about the file being autogenerated""" out_f.write( '\n' '// This is an automatically generated header file.\n' '// Generated with the tools/string_headers.py utility of\n' '// Boost.Metaparse\n' ) class IncludeGuard(object): """Generate include guards""" def __init__(self, out_f): self.out_f = out_f def begin(self): """Generate the beginning part""" name = 'BOOST_METAPARSE_V1_CPP11_IMPL_STRING_HPP' self.out_f.write('#ifndef {0}\n#define {0}\n'.format(name)) write_autogen_info(self.out_f) def end(self): """Generate the closing part""" self.out_f.write('\n#endif\n') def __enter__(self): self.begin() return self def __exit__(self, typ, value, traceback): self.end() def macro_name(name): """Generate the full macro name""" return 'BOOST_METAPARSE_V{0}_{1}'.format(VERSION, name) def define_macro(out_f, (name, args, body), undefine=False, check=True): """Generate a macro definition or undefinition""" if undefine: out_f.write( '#undef {0}\n' .format(macro_name(name)) ) else: if args: arg_list = '({0})'.format(', '.join(args)) else: arg_list = '' if check: out_f.write( '#ifdef {0}\n' '# error {0} already defined.\n' '#endif\n' .format(macro_name(name)) ) out_f.write( '#define {0}{1} {2}\n'.format(macro_name(name), arg_list, body) ) def filename(out_dir, name, undefine=False): """Generate the filename""" if undefine: prefix = 'undef_' else: prefix = '' return os.path.join(out_dir, '{0}{1}.hpp'.format(prefix, name.lower())) def length_limits(max_length_limit, length_limit_step): """Generates the length limits""" string_len = len(str(max_length_limit)) return [ str(i).zfill(string_len) for i in xrange( length_limit_step, max_length_limit + length_limit_step - 1, length_limit_step ) ] def unique_names(count): """Generate count unique variable name""" return ('C{0}'.format(i) for i in xrange(0, count)) def generate_take(out_f, steps, line_prefix): """Generate the take function""" out_f.write( '{0}constexpr inline int take(int n_)\n' '{0}{{\n' '{0} return {1} 0 {2};\n' '{0}}}\n' '\n'.format( line_prefix, ''.join('n_ >= {0} ? {0} : ('.format(s) for s in steps), ')' * len(steps) ) ) def generate_make_string(out_f, max_step): """Generate the make_string template""" steps = [2 ** n for n in xrange(int(math.log(max_step, 2)), -1, -1)] with Namespace( out_f, ['boost', 'metaparse', 'v{0}'.format(VERSION), 'impl'] ) as nsp: generate_take(out_f, steps, nsp.prefix()) out_f.write( '{0}template \n' '{0}struct make_string;\n' '\n' '{0}template ' ' struct make_string<0, 0, Cs...> : string<> {{}};\n' .format(nsp.prefix()) ) disable_sun = False for i in reversed(steps): if i > 64 and not disable_sun: out_f.write('#ifndef __SUNPRO_CC\n') disable_sun = True out_f.write( '{0}template ' ' struct make_string<{2},LenRemaining,{3}Cs...> :' ' concat,' ' typename make_string::type> {{}};\n' .format( nsp.prefix(), ''.join('char {0},'.format(n) for n in unique_names(i)), i, ''.join('{0},'.format(n) for n in unique_names(i)), ','.join(unique_names(i)) ) ) if disable_sun: out_f.write('#endif\n') def generate_string(out_dir, limits): """Generate string.hpp""" max_limit = max((int(v) for v in limits)) with open(filename(out_dir, 'string'), 'wb') as out_f: with IncludeGuard(out_f): out_f.write( '\n' '#include \n' '#include \n' .format(VERSION) ) generate_make_string(out_f, 512) out_f.write( '\n' '#ifndef BOOST_METAPARSE_LIMIT_STRING_SIZE\n' '# error BOOST_METAPARSE_LIMIT_STRING_SIZE not defined\n' '#endif\n' '\n' '#if BOOST_METAPARSE_LIMIT_STRING_SIZE > {0}\n' '# error BOOST_METAPARSE_LIMIT_STRING_SIZE is greater than' ' {0}. To increase the limit run tools/string_headers.py of' ' Boost.Metaparse against your Boost headers.\n' '#endif\n' '\n' .format(max_limit) ) define_macro(out_f, ( 'STRING', ['s'], '{0}::make_string< ' '{0}::take(sizeof(s)-1), sizeof(s)-1-{0}::take(sizeof(s)-1),' 'BOOST_PP_CAT({1}, BOOST_METAPARSE_LIMIT_STRING_SIZE)(s)' '>::type' .format( '::boost::metaparse::v{0}::impl'.format(VERSION), macro_name('I') ) )) out_f.write('\n') for limit in xrange(0, max_limit + 1): out_f.write( '#define {0} {1}\n' .format( macro_name('I{0}'.format(limit)), macro_name('INDEX_STR{0}'.format( min(int(l) for l in limits if int(l) >= limit) )) ) ) out_f.write('\n') prev_macro = None prev_limit = 0 for length_limit in (int(l) for l in limits): this_macro = macro_name('INDEX_STR{0}'.format(length_limit)) out_f.write( '#define {0}(s) {1}{2}\n' .format( this_macro, '{0}(s),'.format(prev_macro) if prev_macro else '', ','.join( '{0}((s), {1})' .format(macro_name('STRING_AT'), i) for i in xrange(prev_limit, length_limit) ) ) ) prev_macro = this_macro prev_limit = length_limit def positive_integer(value): """Throws when the argument is not a positive integer""" val = int(value) if val > 0: return val else: raise argparse.ArgumentTypeError("A positive number is expected") def existing_path(value): """Throws when the path does not exist""" if os.path.exists(value): return value else: raise argparse.ArgumentTypeError("Path {0} not found".format(value)) def main(): """The main function of the script""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( '--boost_dir', required=False, type=existing_path, help='The path to the include/boost directory of Metaparse' ) parser.add_argument( '--max_length_limit', required=False, default=2048, type=positive_integer, help='The maximum supported length limit' ) parser.add_argument( '--length_limit_step', required=False, default=128, type=positive_integer, help='The longest step at which headers are generated' ) args = parser.parse_args() if args.boost_dir is None: tools_path = os.path.dirname(os.path.abspath(__file__)) boost_dir = os.path.join( os.path.dirname(tools_path), 'include', 'boost' ) else: boost_dir = args.boost_dir if args.max_length_limit < 1: sys.stderr.write('Invalid maximum length limit') sys.exit(-1) generate_string( os.path.join( boost_dir, 'metaparse', 'v{0}'.format(VERSION), 'cpp11', 'impl' ), length_limits(args.max_length_limit, args.length_limit_step) ) if __name__ == '__main__': main()