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