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