string_headers.py 10 KB

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