src/detail/format_args.cpp

100.0% Lines (302/302) 100.0% Functions (11/11)
src/detail/format_args.cpp
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/boostorg/url
8 //
9
10
11 #include <boost/url/detail/config.hpp>
12 #include <boost/url/encode.hpp>
13 #include <boost/url/detail/format_args.hpp>
14 #include "boost/url/detail/replacement_field_rule.hpp"
15 #include <boost/url/grammar/delim_rule.hpp>
16 #include <boost/url/grammar/optional_rule.hpp>
17 #include <boost/url/grammar/parse.hpp>
18 #include <boost/url/grammar/tuple_rule.hpp>
19 #include <boost/url/grammar/unsigned_rule.hpp>
20
21 namespace boost {
22 namespace urls {
23 namespace detail {
24
25 std::size_t
26 68 get_uvalue( core::string_view a )
27 {
28 68 core::string_view str(a);
29 68 auto rv = grammar::parse(
30 68 str, grammar::unsigned_rule<std::size_t>{});
31 68 if (rv)
32 2 return *rv;
33 66 return 0;
34 }
35
36 std::size_t
37 68 get_uvalue( char a )
38 {
39 68 core::string_view str(&a, 1);
40 136 return get_uvalue(str);
41 }
42
43 char const*
44 451 formatter<core::string_view>::
45 parse(format_parse_context& ctx)
46 {
47 451 char const* it = ctx.begin();
48 451 char const* end = ctx.end();
49 451 BOOST_ASSERT(it != end);
50
51 // fill / align
52 451 if (end - it > 2)
53 {
54 121 if (*it != '{' &&
55 121 *it != '}' &&
56 23 (*(it + 1) == '<' ||
57 21 *(it + 1) == '>' ||
58 9 *(it + 1) == '^'))
59 {
60 18 fill = *it;
61 18 align = *(it + 1);
62 18 it += 2;
63 }
64 }
65
66 // align
67 451 if (align == '\0' &&
68 433 (*it == '<' ||
69 433 *it == '>' ||
70 433 *it == '^'))
71 {
72 4 align = *it++;
73 }
74
75 // width
76 451 char const* it0 = it;
77 451 constexpr auto width_rule =
78 grammar::variant_rule(
79 grammar::unsigned_rule<std::size_t>{},
80 grammar::tuple_rule(
81 grammar::squelch(
82 grammar::delim_rule('{')),
83 grammar::optional_rule(
84 arg_id_rule),
85 grammar::squelch(
86 grammar::delim_rule('}'))));
87 451 auto rw = grammar::parse(it, end, width_rule);
88 451 if (!rw)
89 {
90 // rewind
91 429 it = it0;
92 }
93 22 else if (align != '\0')
94 {
95 // width is ignored when align is '\0'
96 22 if (rw->index() == 0)
97 {
98 // unsigned_rule
99 12 width = variant2::get<0>(*rw);
100 }
101 else
102 {
103 // arg_id: store the id idx or string
104 10 auto& arg_id = variant2::get<1>(*rw);
105 10 if (!arg_id)
106 {
107 // empty arg_id, use and consume
108 // the next arg idx
109 2 width_idx = ctx.next_arg_id();
110 }
111 8 else if (arg_id->index() == 0)
112 {
113 // string identifier
114 4 width_name = variant2::get<0>(*arg_id);
115 }
116 else
117 {
118 // integer identifier: use the
119 // idx of this format_arg
120 4 width_idx = variant2::get<1>(*arg_id);
121 }
122 }
123 }
124
125 // type is parsed but doesn't have to
126 // be stored for strings
127 451 if (*it == 'c' ||
128 448 *it == 's')
129 {
130 25 ++it;
131 }
132
133 // we should have arrived at the end now
134 451 if (*it != '}')
135 {
136 1 urls::detail::throw_invalid_argument();
137 }
138
139 450 return it;
140 }
141
142 std::size_t
143 226 formatter<core::string_view>::
144 measure(
145 core::string_view str,
146 measure_context& ctx,
147 grammar::lut_chars const& cs) const
148 {
149 226 std::size_t w = width;
150 449 if (width_idx != std::size_t(-1) ||
151 223 !width_name.empty())
152 {
153 5 get_width_from_args(
154 5 width_idx, width_name, ctx.args(), w);
155 }
156
157 226 std::size_t n = ctx.out();
158 226 if (str.size() < w)
159 10 n += measure_one(fill, cs) * (w - str.size());
160
161 226 return n + encoded_size(str, cs);
162 }
163
164 char*
165 224 formatter<core::string_view>::
166 format(core::string_view str, format_context& ctx, grammar::lut_chars const& cs) const
167 {
168 224 std::size_t w = width;
169 445 if (width_idx != std::size_t(-1) ||
170 221 !width_name.empty())
171 {
172 5 get_width_from_args(
173 5 width_idx, width_name, ctx.args(), w);
174 }
175
176 224 std::size_t lpad = 0;
177 224 std::size_t rpad = 0;
178 224 if (str.size() < w)
179 {
180 10 std::size_t pad = w - str.size();
181 10 switch (align)
182 {
183 1 case '<':
184 1 rpad = pad;
185 1 break;
186 6 case '>':
187 6 lpad = pad;
188 6 break;
189 3 case '^':
190 3 lpad = pad / 2;
191 3 rpad = pad - lpad;
192 3 break;
193 }
194 }
195
196 // unsafe `encode`, assuming `out` has
197 // enough capacity
198 224 char* out = ctx.out();
199 252 for (std::size_t i = 0; i < lpad; ++i)
200 28 encode_one(out, fill, cs);
201 1252 for (char c: str)
202 1028 encode_one(out, c, cs);
203 232 for (std::size_t i = 0; i < rpad; ++i)
204 8 encode_one(out, fill, cs);
205 224 return out;
206 }
207
208 void
209 28 get_width_from_args(
210 std::size_t arg_idx,
211 core::string_view arg_name,
212 format_args args,
213 std::size_t& w)
214 {
215 // check arg_id
216 28 format_arg warg;
217 28 if (arg_idx != std::size_t(-1))
218 {
219 // identifier
220 18 warg = args.get(arg_idx);
221 }
222 else
223 {
224 // unsigned integer
225 10 warg = args.get(arg_name);
226 }
227
228 // get unsigned int value from that format arg
229 28 w = warg.value();
230 28 }
231
232 char const*
233 115 integer_formatter_impl::
234 parse(format_parse_context& ctx)
235 {
236 115 char const* it = ctx.begin();
237 115 char const* end = ctx.end();
238 115 BOOST_ASSERT(it != end);
239
240 // fill / align
241 115 if (end - it > 2)
242 {
243 57 if (*it != '{' &&
244 57 *it != '}' &&
245 53 (*(it + 1) == '<' ||
246 49 *(it + 1) == '>' ||
247 27 *(it + 1) == '^'))
248 {
249 30 fill = *it;
250 30 align = *(it + 1);
251 30 it += 2;
252 }
253 }
254
255 // align
256 115 if (align == '\0' &&
257 85 (*it == '<' ||
258 85 *it == '>' ||
259 77 *it == '^'))
260 {
261 12 align = *it++;
262 }
263
264 // sign
265 115 if (*it == '+' ||
266 109 *it == '-' ||
267 109 *it == ' ')
268 {
269 12 sign = *it++;
270 }
271
272 // #
273 115 if (*it == '#')
274 {
275 // alternate form not supported
276 2 ++it;
277 }
278
279 // 0
280 115 if (*it == '0')
281 {
282 8 zeros = *it++;
283 }
284
285 // width
286 115 char const* it0 = it;
287 115 constexpr auto width_rule = grammar::variant_rule(
288 grammar::unsigned_rule<std::size_t>{},
289 grammar::tuple_rule(
290 grammar::squelch(
291 grammar::delim_rule('{')),
292 grammar::optional_rule(
293 arg_id_rule),
294 grammar::squelch(
295 grammar::delim_rule('}'))));
296 115 auto rw = grammar::parse(it, end, width_rule);
297 115 if (!rw)
298 {
299 // rewind
300 73 it = it0;
301 }
302 42 else if (align != '\0')
303 {
304 // width is ignored when align is '\0'
305 42 if (rw->index() == 0)
306 {
307 // unsigned_rule
308 24 width = variant2::get<0>(*rw);
309 }
310 else
311 {
312 // arg_id: store the id idx or string
313 18 auto& arg_id = variant2::get<1>(*rw);
314 18 if (!arg_id)
315 {
316 // empty arg_id, use and consume
317 // the next arg idx
318 4 width_idx = ctx.next_arg_id();
319 }
320 14 else if (arg_id->index() == 0)
321 {
322 // string identifier
323 6 width_name = variant2::get<0>(*arg_id);
324 }
325 else
326 {
327 // integer identifier: use the
328 // idx of this format_arg
329 8 width_idx = variant2::get<1>(*arg_id);
330 }
331 }
332 }
333
334 // type is parsed but doesn't have to
335 // be stored for strings
336 115 if (*it == 'd')
337 {
338 // we don't include other presentation
339 // modes for integers as they are not
340 // recommended or generally used in
341 // urls
342 55 ++it;
343 }
344
345 // we should have arrived at the end now
346 115 if (*it != '}')
347 {
348 1 urls::detail::throw_invalid_argument();
349 }
350
351 114 return it;
352 }
353
354 std::size_t
355 43 integer_formatter_impl::
356 measure(
357 long long int v,
358 measure_context& ctx,
359 grammar::lut_chars const& cs) const
360 {
361 43 std::size_t dn = 0;
362 43 std::size_t n = 0;
363 43 if (v < 0)
364 {
365 2 dn += measure_one('-', cs);
366 2 ++n;
367 }
368 41 else if (sign != '-')
369 {
370 4 dn += measure_one(sign, cs);
371 4 ++n;
372 }
373 // Use unsigned to avoid UB when v == LLONG_MIN
374 43 unsigned long long int uv = v < 0
375 43 ? 0ull - static_cast<unsigned long long int>(v)
376 : static_cast<unsigned long long int>(v);
377 do
378 {
379 102 int d = static_cast<int>(uv % 10);
380 102 uv /= 10;
381 102 dn += measure_one('0' + static_cast<char>(d), cs);
382 102 ++n;
383 }
384 102 while (uv > 0);
385
386 43 std::size_t w = width;
387 83 if (width_idx != std::size_t(-1) ||
388 40 !width_name.empty())
389 {
390 5 get_width_from_args(
391 5 width_idx, width_name, ctx.args(), w);
392 }
393 43 if (w > n)
394 {
395 12 if (!zeros)
396 9 dn += measure_one(fill, cs) * (w - n);
397 else
398 3 dn += measure_one('0', cs) * (w - n);
399 }
400 86 return ctx.out() + dn;
401 }
402
403 std::size_t
404 14 integer_formatter_impl::
405 measure(
406 unsigned long long int v,
407 measure_context& ctx,
408 grammar::lut_chars const& cs) const
409 {
410 14 std::size_t dn = 0;
411 14 std::size_t n = 0;
412 14 if (sign != '-')
413 {
414 2 dn += measure_one(sign, cs);
415 2 ++n;
416 }
417 do
418 {
419 53 int d = v % 10;
420 53 v /= 10;
421 53 dn += measure_one('0' + static_cast<char>(d), cs);
422 53 ++n;
423 }
424 53 while (v != 0);
425
426 14 std::size_t w = width;
427 25 if (width_idx != std::size_t(-1) ||
428 11 !width_name.empty())
429 {
430 4 get_width_from_args(
431 4 width_idx, width_name, ctx.args(), w);
432 }
433 14 if (w > n)
434 {
435 8 if (!zeros)
436 7 dn += measure_one(fill, cs) * (w - n);
437 else
438 1 dn += measure_one('0', cs) * (w - n);
439 }
440 28 return ctx.out() + dn;
441 }
442
443 char*
444 43 integer_formatter_impl::
445 format(
446 long long int v,
447 format_context& ctx,
448 grammar::lut_chars const& cs) const
449 {
450 // get n digits
451 // Use unsigned to avoid UB when v == LLONG_MIN
452 43 bool const neg = v < 0;
453 43 unsigned long long int uv = neg
454 43 ? 0ull - static_cast<unsigned long long int>(v)
455 : static_cast<unsigned long long int>(v);
456 43 unsigned long long int uv0 = uv;
457 43 unsigned long long int p = 1;
458 43 std::size_t n = 0;
459 43 if (neg || sign != '-')
460 {
461 6 ++n;
462 }
463 do
464 {
465 102 if (uv >= 10)
466 59 p *= 10;
467 102 uv /= 10;
468 102 ++n;
469 }
470 102 while (uv > 0);
471 static constexpr auto m =
472 std::numeric_limits<long long int>::digits10;
473 43 BOOST_ASSERT(n <= m + 2);
474 ignore_unused(m);
475
476 // get pad
477 43 std::size_t w = width;
478 83 if (width_idx != std::size_t(-1) ||
479 40 !width_name.empty())
480 {
481 5 get_width_from_args(
482 5 width_idx, width_name, ctx.args(), w);
483 }
484 43 std::size_t lpad = 0;
485 43 std::size_t rpad = 0;
486 43 if (w > n)
487 {
488 12 std::size_t pad = w - n;
489 12 if (zeros)
490 {
491 3 lpad = pad;
492 }
493 else
494 {
495 9 switch (align)
496 {
497 1 case '<':
498 1 rpad = pad;
499 1 break;
500 6 case '>':
501 6 lpad = pad;
502 6 break;
503 2 case '^':
504 2 lpad = pad / 2;
505 2 rpad = pad - lpad;
506 2 break;
507 }
508 }
509 }
510
511 // write
512 43 uv = uv0;
513 43 char* out = ctx.out();
514 43 if (!zeros)
515 {
516 68 for (std::size_t i = 0; i < lpad; ++i)
517 28 encode_one(out, fill, cs);
518 }
519 43 if (neg)
520 {
521 2 encode_one(out, '-', cs);
522 2 --n;
523 }
524 41 else if (sign != '-')
525 {
526 4 encode_one(out, sign, cs);
527 4 --n;
528 }
529 43 if (zeros)
530 {
531 13 for (std::size_t i = 0; i < lpad; ++i)
532 10 encode_one(out, '0', cs);
533 }
534 145 while (n)
535 {
536 102 unsigned long long int d = uv / p;
537 102 encode_one(out, '0' + static_cast<char>(d), cs);
538 102 --n;
539 102 uv %= p;
540 102 p /= 10;
541 }
542 43 if (!zeros)
543 {
544 48 for (std::size_t i = 0; i < rpad; ++i)
545 8 encode_one(out, fill, cs);
546 }
547 43 return out;
548 }
549
550 char*
551 14 integer_formatter_impl::
552 format(
553 unsigned long long int v,
554 format_context& ctx,
555 grammar::lut_chars const& cs) const
556 {
557 // get n digits
558 14 unsigned long long int v0 = v;
559 14 unsigned long long int p = 1;
560 14 std::size_t n = 0;
561 14 if (sign != '-')
562 {
563 2 ++n;
564 }
565 do
566 {
567 53 if (v >= 10)
568 39 p *= 10;
569 53 v /= 10;
570 53 ++n;
571 }
572 53 while (v > 0);
573 static constexpr auto m =
574 std::numeric_limits<unsigned long long int>::digits10;
575 14 BOOST_ASSERT(n <= m + 2);
576 ignore_unused(m);
577
578 // get pad
579 14 std::size_t w = width;
580 25 if (width_idx != std::size_t(-1) ||
581 11 !width_name.empty())
582 {
583 4 get_width_from_args(
584 4 width_idx, width_name, ctx.args(), w);
585 }
586 14 std::size_t lpad = 0;
587 14 std::size_t rpad = 0;
588 14 if (w > n)
589 {
590 8 std::size_t pad = w - n;
591 8 if (zeros)
592 {
593 1 lpad = pad;
594 }
595 else
596 {
597 7 switch (align)
598 {
599 1 case '<':
600 1 rpad = pad;
601 1 break;
602 5 case '>':
603 5 lpad = pad;
604 5 break;
605 1 case '^':
606 1 lpad = pad / 2;
607 1 rpad = pad - lpad;
608 1 break;
609 }
610 }
611 }
612
613 // write
614 14 v = v0;
615 14 char* out = ctx.out();
616 14 if (!zeros)
617 {
618 35 for (std::size_t i = 0; i < lpad; ++i)
619 22 encode_one(out, fill, cs);
620 }
621 14 if (sign != '-')
622 {
623 2 encode_one(out, sign, cs);
624 2 --n;
625 }
626 14 if (zeros)
627 {
628 5 for (std::size_t i = 0; i < lpad; ++i)
629 4 encode_one(out, '0', cs);
630 }
631 67 while (n)
632 {
633 53 unsigned long long int d = v / p;
634 53 encode_one(out, '0' + static_cast<char>(d), cs);
635 53 --n;
636 53 v %= p;
637 53 p /= 10;
638 }
639 14 if (!zeros)
640 {
641 19 for (std::size_t i = 0; i < rpad; ++i)
642 6 encode_one(out, fill, cs);
643 }
644 14 return out;
645 }
646
647 } // detail
648 } // urls
649 } // boost
650
651