]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // Boost.TypeErasure library |
2 | // | |
3 | // Copyright 2012 Steven Watanabe | |
4 | // | |
5 | // Distributed under the Boost Software License Version 1.0. (See | |
6 | // accompanying file LICENSE_1_0.txt or copy at | |
7 | // http://www.boost.org/LICENSE_1_0.txt) | |
8 | // | |
9 | // $Id$ | |
10 | ||
11 | //[printf | |
12 | /*` | |
13 | (For the source of this example see | |
14 | [@boost:/libs/type_erasure/example/printf.cpp printf.cpp]) | |
15 | ||
16 | This example uses the library to implement a type safe printf. | |
17 | ||
18 | [note This example uses C++11 features. You'll need a | |
19 | recent compiler for it to work.] | |
20 | */ | |
21 | ||
22 | #include <boost/type_erasure/builtin.hpp> | |
23 | #include <boost/type_erasure/operators.hpp> | |
24 | #include <boost/type_erasure/any_cast.hpp> | |
25 | #include <boost/type_erasure/any.hpp> | |
26 | #include <boost/mpl/vector.hpp> | |
27 | #include <boost/io/ios_state.hpp> | |
28 | #include <iostream> | |
29 | #include <sstream> | |
30 | #include <iomanip> | |
31 | #include <vector> | |
32 | #include <string> | |
33 | ||
34 | namespace mpl = boost::mpl; | |
35 | using namespace boost::type_erasure; | |
36 | using namespace boost::io; | |
37 | ||
38 | // We capture the arguments by reference and require nothing | |
39 | // except that each one must provide a stream insertion operator. | |
40 | typedef any< | |
41 | mpl::vector< | |
42 | typeid_<>, | |
43 | ostreamable<> | |
44 | >, | |
45 | const _self& | |
46 | > any_printable; | |
47 | typedef std::vector<any_printable> print_storage; | |
48 | ||
49 | // Forward declaration of the implementation function | |
50 | void print_impl(std::ostream& os, const char * format, const print_storage& args); | |
51 | ||
52 | ||
53 | // | |
54 | // Writes values to a stream like the classic C printf function. The | |
55 | // arguments are formatted based on specifiers in the format string, | |
56 | // which match the pattern: | |
57 | // | |
58 | // '%' [ argument-number '$' ] flags * [ width ] [ '.' precision ] [ type-code ] format-specifier | |
59 | // | |
60 | // Other characters in the format string are written to the stream unchanged. | |
61 | // In addition the sequence, "%%" can be used to print a literal '%' character. | |
62 | // Each component is explained in detail below | |
63 | // | |
64 | // argument-number: | |
65 | // The value must be between 1 and sizeof... T. It indicates the | |
66 | // index of the argument to be formatted. If no index is specified | |
67 | // the arguments will be processed sequentially. If an index is | |
68 | // specified for one argument, then it must be specified for every argument. | |
69 | // | |
70 | // flags: | |
71 | // Consists of zero or more of the following: | |
72 | // '-': Left justify the argument | |
73 | // '+': Print a plus sign for positive integers | |
74 | // '0': Use leading 0's to pad instead of filling with spaces. | |
75 | // ' ': If the value doesn't begin with a sign, prepend a space | |
76 | // '#': Print 0x or 0 for hexadecimal and octal numbers. | |
77 | // | |
78 | // width: | |
79 | // Indicates the minimum width to print. This can be either | |
80 | // an integer or a '*'. an asterisk means to read the next | |
81 | // argument (which must have type int) as the width. | |
82 | // | |
83 | // precision: | |
84 | // For numeric arguments, indicates the number of digits to print. For | |
85 | // strings (%s) the precision indicates the maximum number of characters | |
86 | // to print. Longer strings will be truncated. As with width | |
87 | // this can be either an integer or a '*'. an asterisk means | |
88 | // to read the next argument (which must have type int) as | |
89 | // the width. If both the width and the precision are specified | |
90 | // as '*', the width is read first. | |
91 | // | |
92 | // type-code: | |
93 | // This is ignored, but provided for compatibility with C printf. | |
94 | // | |
95 | // format-specifier: | |
96 | // Must be one of the following characters: | |
97 | // d, i, u: The argument is formatted as a decimal integer | |
98 | // o: The argument is formatted as an octal integer | |
99 | // x, X: The argument is formatted as a hexadecimal integer | |
100 | // p: The argument is formatted as a pointer | |
101 | // f: The argument is formatted as a fixed point decimal | |
102 | // e, E: The argument is formatted in exponential notation | |
103 | // g, G: The argument is formatted as either fixed point or using | |
104 | // scientific notation depending on its magnitude | |
105 | // c: The argument is formatted as a character | |
106 | // s: The argument is formatted as a string | |
107 | // | |
108 | template<class... T> | |
109 | void print(std::ostream& os, const char * format, const T&... t) | |
110 | { | |
111 | // capture the arguments | |
112 | print_storage args = { any_printable(t)... }; | |
113 | // and forward to the real implementation | |
114 | print_impl(os, format, args); | |
115 | } | |
116 | ||
117 | // This overload of print with no explicit stream writes to std::cout. | |
118 | template<class... T> | |
119 | void print(const char * format, const T&... t) | |
120 | { | |
121 | print(std::cout, format, t...); | |
122 | } | |
123 | ||
124 | // The implementation from here on can be separately compiled. | |
125 | ||
126 | // utility function to parse an integer | |
127 | int parse_int(const char *& format) { | |
128 | int result = 0; | |
129 | while(char ch = *format) { | |
130 | switch(ch) { | |
131 | case '0': case '1': case '2': case '3': case '4': | |
132 | case '5': case '6': case '7': case '8': case '9': | |
133 | result = result * 10 + (ch - '0'); | |
134 | break; | |
135 | default: return result; | |
136 | } | |
137 | ++format; | |
138 | } | |
139 | return result; | |
140 | } | |
141 | ||
142 | // printf implementation | |
143 | void print_impl(std::ostream& os, const char * format, const print_storage& args) { | |
144 | int idx = 0; | |
145 | ios_flags_saver savef_outer(os, std::ios_base::dec); | |
146 | bool has_positional = false; | |
147 | bool has_indexed = false; | |
148 | while(char ch = *format++) { | |
149 | if (ch == '%') { | |
150 | if (*format == '%') { os << '%'; continue; } | |
151 | ||
152 | ios_flags_saver savef(os); | |
153 | ios_precision_saver savep(os); | |
154 | ios_fill_saver savefill(os); | |
155 | ||
156 | int precision = 0; | |
157 | bool pad_space = false; | |
158 | bool pad_zero = false; | |
159 | ||
160 | // parse argument index | |
161 | if (*format != '0') { | |
162 | int i = parse_int(format); | |
163 | if (i != 0) { | |
164 | if(*format == '$') { | |
165 | idx = i - 1; | |
166 | has_indexed = true; | |
167 | ++format; | |
168 | } else { | |
169 | os << std::setw(i); | |
170 | has_positional = true; | |
171 | goto parse_precision; | |
172 | } | |
173 | } else { | |
174 | has_positional = true; | |
175 | } | |
176 | } else { | |
177 | has_positional = true; | |
178 | } | |
179 | ||
180 | // Parse format modifiers | |
181 | while((ch = *format)) { | |
182 | switch(ch) { | |
183 | case '-': os << std::left; break; | |
184 | case '+': os << std::showpos; break; | |
185 | case '0': pad_zero = true; break; | |
186 | case ' ': pad_space = true; break; | |
187 | case '#': os << std::showpoint << std::showbase; break; | |
188 | default: goto parse_width; | |
189 | } | |
190 | ++format; | |
191 | } | |
192 | ||
193 | parse_width: | |
194 | int width; | |
195 | if (*format == '*') { | |
196 | ++format; | |
197 | width = any_cast<int>(args.at(idx++)); | |
198 | } else { | |
199 | width = parse_int(format); | |
200 | } | |
201 | os << std::setw(width); | |
202 | ||
203 | parse_precision: | |
204 | if (*format == '.') { | |
205 | ++format; | |
206 | if (*format == '*') { | |
207 | ++format; | |
208 | precision = any_cast<int>(args.at(idx++)); | |
209 | } else { | |
210 | precision = parse_int(format); | |
211 | } | |
212 | os << std::setprecision(precision); | |
213 | } | |
214 | ||
215 | // parse (and ignore) the type modifier | |
216 | switch(*format) { | |
217 | case 'h': ++format; if(*format == 'h') ++format; break; | |
218 | case 'l': ++format; if(*format == 'l') ++format; break; | |
219 | case 'j': | |
220 | case 'L': | |
221 | case 'q': | |
222 | case 't': | |
223 | case 'z': | |
224 | ++format; break; | |
225 | } | |
226 | ||
227 | std::size_t truncate = 0; | |
228 | ||
229 | // parse the format code | |
230 | switch(*format++) { | |
231 | case 'd': case 'i': case 'u': os << std::dec; break; | |
232 | case 'o': os << std::oct; break; | |
233 | case 'p': case 'x': os << std::hex; break; | |
234 | case 'X': os << std::uppercase << std::hex; break; | |
235 | case 'f': os << std::fixed; break; | |
236 | case 'e': os << std::scientific; break; | |
237 | case 'E': os << std::uppercase << std::scientific; break; | |
238 | case 'g': break; | |
239 | case 'G': os << std::uppercase; break; | |
240 | case 'c': case 'C': break; | |
241 | case 's': case 'S': truncate = precision; os << std::setprecision(6); break; | |
242 | default: assert(!"Bad format string"); | |
243 | } | |
244 | ||
245 | if (pad_zero && !(os.flags() & std::ios_base::left)) { | |
246 | os << std::setfill('0') << std::internal; | |
247 | pad_space = false; | |
248 | } | |
249 | ||
250 | if (truncate != 0 || pad_space) { | |
251 | // These can't be handled by std::setw. Write to a stringstream and | |
252 | // pad/truncate manually. | |
253 | std::ostringstream oss; | |
254 | oss.copyfmt(os); | |
255 | oss << args.at(idx++); | |
256 | std::string data = oss.str(); | |
257 | ||
258 | if (pad_space) { | |
259 | if (data.empty() || (data[0] != '+' && data[0] != '-' && data[0] != ' ')) { | |
260 | os << ' '; | |
261 | } | |
262 | } | |
263 | if (truncate != 0 && data.size() > truncate) { | |
264 | data.resize(truncate); | |
265 | } | |
266 | os << data; | |
267 | } else { | |
268 | os << args.at(idx++); | |
269 | } | |
270 | ||
271 | // we can't have both positional and indexed arguments in | |
272 | // the format string. | |
273 | assert(has_positional ^ has_indexed); | |
274 | ||
275 | } else { | |
276 | std::cout << ch; | |
277 | } | |
278 | } | |
279 | } | |
280 | ||
281 | int main() { | |
282 | print("int: %d\n", 10); | |
283 | print("int: %0#8X\n", 0xA56E); | |
284 | print("double: %g\n", 3.14159265358979323846); | |
285 | print("double: %f\n", 3.14159265358979323846); | |
286 | print("double: %+20.9e\n", 3.14159265358979323846); | |
287 | print("double: %0+20.9g\n", 3.14159265358979323846); | |
288 | print("double: %*.*g\n", 20, 5, 3.14159265358979323846); | |
289 | print("string: %.10s\n", "Hello World!"); | |
290 | print("double: %2$*.*g int: %1$d\n", 10, 20, 5, 3.14159265358979323846); | |
291 | } | |
292 | ||
293 | //] |