printf_style_double_format.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. // Copyright (c) 2001-2010 Hartmut Kaiser
  2. //
  3. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  4. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  5. // The main purpose of this example is to show how a single container type can
  6. // be formatted using different output grammars.
  7. #include <boost/config/warning_disable.hpp>
  8. #include <boost/spirit/include/qi.hpp>
  9. #include <boost/spirit/include/karma.hpp>
  10. #include <boost/fusion/include/adapt_struct.hpp>
  11. #include <cmath>
  12. using namespace boost::spirit;
  13. ///////////////////////////////////////////////////////////////////////////////
  14. // This policy allows to use printf style formatting specifiers for Karma
  15. // floating point generators. This policy understands the following format:
  16. //
  17. // The format string must conform to the following format, otherwise a
  18. // std::runtime_error will be thrown:
  19. //
  20. // %[flags][fill][width][.precision]type
  21. //
  22. // where:
  23. // flags (only one possible):
  24. // +: Always denote the sign '+' or '-' of a number
  25. // -: Left-align the output
  26. // fill:
  27. // 0: Uses 0 instead of spaces to left-fill a fixed-length field
  28. // width:
  29. // number: Left-pad the output with spaces until it is at least number
  30. // characters wide. if number has a leading '0', that is
  31. // interpreted as a 'fill', the padding is done with '0'
  32. // characters instead of spaces.
  33. // precision:
  34. // number: Causes the decimal portion of the output to be expressed
  35. // in at least number digits
  36. // type (only one possible):
  37. // e: force scientific notation, with a lowercase "e"
  38. // E: force scientific notation, with a uppercase "E"
  39. // f: floating point format
  40. // g: use %e or %f, whichever is shorter
  41. // G: use %E or %f, whichever is shorter
  42. //
  43. ///////////////////////////////////////////////////////////////////////////////
  44. // define a data structure and a corresponding parser to hold the formatting
  45. // information extracted from the format specification string
  46. namespace client
  47. {
  48. struct format_data
  49. {
  50. char flag;
  51. char fill;
  52. int width;
  53. int precision;
  54. char type;
  55. };
  56. }
  57. // We need to tell fusion about our format_data struct
  58. // to make it a first-class fusion citizen
  59. BOOST_FUSION_ADAPT_STRUCT(
  60. client::format_data,
  61. (char, flag)
  62. (char, fill)
  63. (int, width)
  64. (int, precision)
  65. (char, type)
  66. )
  67. namespace client
  68. {
  69. ///////////////////////////////////////////////////////////////////////////
  70. // Grammar for format specification string as described above
  71. template <typename Iterator>
  72. struct format_grammar : qi::grammar<Iterator, format_data()>
  73. {
  74. format_grammar() : format_grammar::base_type(format)
  75. {
  76. using qi::uint_;
  77. using qi::attr;
  78. using ascii::char_;
  79. using ascii::no_case;
  80. format %= '%' >> flags >> fill >> width >> prec >> type;
  81. // default flags is right aligned
  82. flags = char_('+') | char_('-') | attr(' ');
  83. fill = char_('0') | attr(' '); // default fill is space
  84. width = uint_ | attr(-1);
  85. prec = '.' >> uint_ | attr(3); // default is 3 digits
  86. type = no_case[char_('e')] | char_('f') | no_case[char_('g')];
  87. };
  88. qi::rule<Iterator, format_data()> format;
  89. qi::rule<Iterator, char()> flags;
  90. qi::rule<Iterator, char()> fill;
  91. qi::rule<Iterator, int()> width;
  92. qi::rule<Iterator, int()> prec;
  93. qi::rule<Iterator, char()> type;
  94. };
  95. }
  96. ///////////////////////////////////////////////////////////////////////////////
  97. // real_policies implementation allowing to use a printf style format
  98. // specification for Karma floating pointing number generators
  99. template <typename T>
  100. struct format_policies : karma::real_policies<T>
  101. {
  102. typedef karma::real_policies<T> base_policy_type;
  103. ///////////////////////////////////////////////////////////////////////////
  104. // This real_policies implementation requires the output_iterator to
  105. // implement buffering and character counting. This needs to be reflected
  106. // in the properties exposed by the generator
  107. typedef boost::mpl::int_<
  108. karma::generator_properties::countingbuffer
  109. > properties;
  110. ///////////////////////////////////////////////////////////////////////////
  111. format_policies(char const* fmt = "%f")
  112. {
  113. char const* last = fmt;
  114. while (*last)
  115. last++;
  116. client::format_grammar<char const*> g;
  117. if (!qi::parse(fmt, last, g, format_))
  118. throw std::runtime_error("bad format string");
  119. }
  120. ///////////////////////////////////////////////////////////////////////////
  121. // returns the overall format: scientific or fixed
  122. int floatfield(T n) const
  123. {
  124. if (format_.type == 'e' || format_.type == 'E')
  125. return base_policy_type::fmtflags::scientific;
  126. if (format_.type == 'f')
  127. return base_policy_type::fmtflags::fixed;
  128. BOOST_ASSERT(format_.type == 'g' || format_.type == 'G');
  129. return this->base_policy_type::floatfield(n);
  130. }
  131. ///////////////////////////////////////////////////////////////////////////
  132. // returns whether to emit a sign even for non-negative numbers
  133. bool const force_sign(T) const
  134. {
  135. return format_.flag == '+';
  136. }
  137. ///////////////////////////////////////////////////////////////////////////
  138. // returns the number of required digits for the fractional part
  139. unsigned precision(T) const
  140. {
  141. return format_.precision;
  142. }
  143. ///////////////////////////////////////////////////////////////////////////
  144. // emit the decimal dot
  145. template <typename OutputIterator>
  146. static bool dot (OutputIterator& sink, T n, unsigned precision)
  147. {
  148. // don't print the dot if no fractional digits are to be emitted
  149. if (precision == 0)
  150. return true;
  151. return base_policy_type::dot(sink, n, precision);
  152. }
  153. template <typename CharEncoding, typename Tag, typename OutputIterator>
  154. bool exponent (OutputIterator& sink, long n) const
  155. {
  156. if (format_.type == 'E' || format_.type == 'G') {
  157. // print exponent symbol in upper case
  158. return this->base_policy_type::
  159. template exponent<char_encoding::ascii, tag::upper>(sink, n);
  160. }
  161. return this->base_policy_type::
  162. template exponent<CharEncoding, Tag>(sink, n);
  163. }
  164. ///////////////////////////////////////////////////////////////////////////
  165. // this gets called by the numeric generators at the top level, it allows
  166. // to do alignment and other high level things
  167. template <typename Inserter, typename OutputIterator, typename Policies>
  168. bool call (OutputIterator& sink, T n, Policies const& p) const
  169. {
  170. bool r = false;
  171. if (format_.flag == '-') { // left align
  172. // wrap the given output iterator to allow counting
  173. karma::detail::enable_counting<OutputIterator> counting(sink);
  174. // first generate the actual floating point number
  175. r = Inserter::call_n(sink, n, p);
  176. // pad the output until the max width is reached
  177. while(r && int(counting.count()) < format_.width)
  178. r = karma::generate(sink, ' ');
  179. }
  180. else { // right align
  181. // wrap the given output iterator to allow left padding
  182. karma::detail::enable_buffering<OutputIterator> buffering(
  183. sink, format_.width);
  184. // first generate the actual floating point number
  185. {
  186. karma::detail::disable_counting<OutputIterator> nocounting(sink);
  187. r = Inserter::call_n(sink, n, p);
  188. }
  189. buffering.disable(); // do not perform buffering any more
  190. // generate the left padding
  191. karma::detail::enable_counting<OutputIterator> counting(
  192. sink, buffering.buffer_size());
  193. while(r && int(counting.count()) < format_.width)
  194. r = karma::generate(sink, format_.fill);
  195. // copy the buffered output to the target output iterator
  196. if (r)
  197. buffering.buffer_copy();
  198. }
  199. return r;
  200. }
  201. client::format_data format_;
  202. };
  203. ///////////////////////////////////////////////////////////////////////////////
  204. // This is the generator usable in any Karma output format expression, it needs
  205. // to be utilized as
  206. //
  207. // generate(sink, real("%6.3f"), 3.1415926536); // prints: ' 3.142'
  208. //
  209. // and it supports the format specification as described above.
  210. typedef karma::real_generator<double, format_policies<double> > real;
  211. ///////////////////////////////////////////////////////////////////////////////
  212. int main()
  213. {
  214. std::cout << "/////////////////////////////////////////////////////////////\n\n";
  215. std::cout << "A format driven floating point number generator for Spirit...\n\n";
  216. std::cout << "/////////////////////////////////////////////////////////////\n\n";
  217. std::cout << "Give me a printf style format\n";
  218. std::cout << "Type [enter] to quit\n\n";
  219. std::string str;
  220. while (getline(std::cin, str))
  221. {
  222. if (str.empty())
  223. break;
  224. try {
  225. std::string generated;
  226. std::back_insert_iterator<std::string> sink(generated);
  227. if (!karma::generate(sink, real(str.c_str()), 4*std::atan(1.0)))
  228. {
  229. std::cout << "-------------------------\n";
  230. std::cout << "Generating failed\n";
  231. std::cout << "-------------------------\n";
  232. }
  233. else
  234. {
  235. std::cout << ">" << generated << "<\n";
  236. }
  237. }
  238. catch (std::runtime_error const&) {
  239. std::cout << "-------------------------\n";
  240. std::cout << "Invalid format specified!\n";
  241. std::cout << "-------------------------\n";
  242. }
  243. }
  244. return 0;
  245. }