1use core::fmt;
4
5use crate::{
6 format::{Fmt, FormatArgument, Pad, StrFormat, StrLength},
7 utils::{assert_is_ascii, count_chars, ClippedStr},
8 CompileArgs,
9};
10
11#[derive(Debug, Clone, Copy)]
12enum ArgumentInner<'a> {
13 Str(&'a str, Option<StrFormat>),
14 Char(char),
15 Int(i128),
16 UnsignedInt(u128),
17}
18
19impl ArgumentInner<'_> {
20 const fn formatted_len(&self) -> StrLength {
21 match self {
22 Self::Str(s, None) => StrLength::for_str(s),
23 Self::Str(s, Some(fmt)) => match ClippedStr::new(s, fmt.clip_at) {
24 ClippedStr::Full(_) => StrLength::for_str(s),
25 ClippedStr::Clipped(bytes) => StrLength {
26 bytes: bytes.len() + fmt.using.len(),
27 chars: fmt.clip_at + count_chars(fmt.using),
28 },
29 },
30 Self::Char(c) => StrLength::for_char(*c),
31 Self::Int(value) => {
32 let bytes = (*value < 0) as usize + log_10_ceil(value.unsigned_abs());
33 StrLength::both(bytes)
34 }
35 Self::UnsignedInt(value) => StrLength::both(log_10_ceil(*value)),
36 }
37 }
38}
39
40#[doc(hidden)] #[derive(Debug, Clone, Copy)]
43pub struct Argument<'a> {
44 inner: ArgumentInner<'a>,
45 pad: Option<Pad>,
46}
47
48impl Argument<'_> {
49 pub const fn formatted_len(&self) -> usize {
51 let non_padded_len = self.inner.formatted_len();
52 if let Some(pad) = &self.pad {
53 if pad.width > non_padded_len.chars {
54 let pad_char_count = pad.width - non_padded_len.chars;
55 pad_char_count * pad.using.len_utf8() + non_padded_len.bytes
56 } else {
57 non_padded_len.bytes
59 }
60 } else {
61 non_padded_len.bytes
62 }
63 }
64}
65
66const fn log_10_ceil(mut value: u128) -> usize {
67 if value == 0 {
68 return 1;
69 }
70
71 let mut log = 0;
72 while value > 0 {
73 value /= 10;
74 log += 1;
75 }
76 log
77}
78
79impl<const CAP: usize> CompileArgs<CAP> {
80 const fn write_u128(self, mut value: u128) -> Self {
81 let new_len = self.len + log_10_ceil(value);
82 let mut buffer = self.buffer;
83 let mut pos = new_len - 1;
84
85 loop {
86 buffer[pos] = b'0' + (value % 10) as u8;
87 if pos == self.len {
88 break;
89 }
90 value /= 10;
91 pos -= 1;
92 }
93 Self {
94 buffer,
95 len: new_len,
96 }
97 }
98
99 const fn write_i128(self, value: i128) -> Self {
100 let this = if value < 0 {
101 self.write_char('-')
102 } else {
103 self
104 };
105 this.write_u128(value.unsigned_abs())
106 }
107
108 pub(crate) const fn format_arg(mut self, arg: Argument) -> Self {
109 let pad_after = 'compute_pad: {
110 if let Some(pad) = &arg.pad {
111 let non_padded_len = arg.inner.formatted_len();
113 if pad.width > non_padded_len.chars {
114 let (pad_before, pad_after) = pad.compute_padding(non_padded_len.chars);
115 let mut count = 0;
116 while count < pad_before {
117 self = self.write_char(pad.using);
118 count += 1;
119 }
120 break 'compute_pad Some((pad_after, pad.using));
121 }
122 }
123 None
124 };
125
126 self = match arg.inner {
127 ArgumentInner::Str(s, fmt) => self.write_str(s, fmt),
128 ArgumentInner::Char(c) => self.write_char(c),
130 ArgumentInner::Int(value) => self.write_i128(value),
131 ArgumentInner::UnsignedInt(value) => self.write_u128(value),
132 };
133 if let Some((pad_after, using)) = pad_after {
134 let mut count = 0;
135 while count < pad_after {
136 self = self.write_char(using);
137 count += 1;
138 }
139 }
140 self
141 }
142}
143
144#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
170pub struct Ascii<'a>(pub(crate) &'a str);
171
172impl<'a> Ascii<'a> {
173 pub const fn new(s: &'a str) -> Self {
179 assert_is_ascii(s);
180 Self(s)
181 }
182}
183
184#[doc(hidden)] pub struct ArgumentWrapper<T: FormatArgument> {
187 value: T,
188 fmt: Option<Fmt<T>>,
189}
190
191impl<T> fmt::Debug for ArgumentWrapper<T>
192where
193 T: FormatArgument + fmt::Debug,
194 T::Details: fmt::Debug,
195{
196 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
197 formatter
198 .debug_struct("ArgumentWrapper")
199 .field("value", &self.value)
200 .field("fmt", &self.fmt)
201 .finish()
202 }
203}
204
205impl<T: FormatArgument> ArgumentWrapper<T> {
206 pub const fn new(value: T) -> Self {
207 Self { value, fmt: None }
208 }
209
210 #[must_use]
211 pub const fn with_fmt(mut self, fmt: Fmt<T>) -> Self {
212 self.fmt = Some(fmt);
213 self
214 }
215}
216
217impl<'a> ArgumentWrapper<&'a str> {
218 pub const fn into_argument(self) -> Argument<'a> {
220 let (str_fmt, pad) = match self.fmt {
221 Some(Fmt { details, pad, .. }) => (Some(details), pad),
222 None => (None, None),
223 };
224 Argument {
225 inner: ArgumentInner::Str(self.value, str_fmt),
226 pad,
227 }
228 }
229}
230
231impl<'a> ArgumentWrapper<Ascii<'a>> {
232 pub const fn into_argument(self) -> Argument<'a> {
234 let (str_fmt, pad) = match self.fmt {
235 Some(Fmt { details, pad, .. }) => (Some(details), pad),
236 None => (None, None),
237 };
238 Argument {
239 inner: ArgumentInner::Str(self.value.0, str_fmt),
240 pad,
241 }
242 }
243}
244
245impl<'a, const CAP: usize> ArgumentWrapper<&'a CompileArgs<CAP>> {
246 pub const fn into_argument(self) -> Argument<'a> {
248 Argument {
249 inner: ArgumentInner::Str(self.value.as_str(), None),
250 pad: None,
251 }
252 }
253}
254
255impl ArgumentWrapper<i128> {
256 pub const fn into_argument(self) -> Argument<'static> {
258 let pad = match self.fmt {
259 Some(Fmt { pad, .. }) => pad,
260 None => None,
261 };
262 Argument {
263 inner: ArgumentInner::Int(self.value),
264 pad,
265 }
266 }
267}
268
269macro_rules! impl_argument_wrapper_for_int {
270 ($int:ty) => {
271 impl ArgumentWrapper<$int> {
272 pub const fn into_argument(self) -> Argument<'static> {
274 let pad = match self.fmt {
275 Some(Fmt { pad, .. }) => pad,
276 None => None,
277 };
278 Argument {
279 inner: ArgumentInner::Int(self.value as i128),
280 pad,
281 }
282 }
283 }
284 };
285}
286
287impl_argument_wrapper_for_int!(i8);
288impl_argument_wrapper_for_int!(i16);
289impl_argument_wrapper_for_int!(i32);
290impl_argument_wrapper_for_int!(i64);
291impl_argument_wrapper_for_int!(isize);
292
293impl ArgumentWrapper<u128> {
294 pub const fn into_argument(self) -> Argument<'static> {
296 let pad = match self.fmt {
297 Some(Fmt { pad, .. }) => pad,
298 None => None,
299 };
300 Argument {
301 inner: ArgumentInner::UnsignedInt(self.value),
302 pad,
303 }
304 }
305}
306
307macro_rules! impl_argument_wrapper_for_uint {
308 ($uint:ty) => {
309 impl ArgumentWrapper<$uint> {
310 pub const fn into_argument(self) -> Argument<'static> {
312 let pad = match self.fmt {
313 Some(Fmt { pad, .. }) => pad,
314 None => None,
315 };
316 Argument {
317 inner: ArgumentInner::UnsignedInt(self.value as u128),
318 pad,
319 }
320 }
321 }
322 };
323}
324
325impl_argument_wrapper_for_uint!(u8);
326impl_argument_wrapper_for_uint!(u16);
327impl_argument_wrapper_for_uint!(u32);
328impl_argument_wrapper_for_uint!(u64);
329impl_argument_wrapper_for_uint!(usize);
330
331impl ArgumentWrapper<char> {
332 pub const fn into_argument(self) -> Argument<'static> {
334 let pad = match self.fmt {
335 Some(Fmt { pad, .. }) => pad,
336 None => None,
337 };
338 Argument {
339 inner: ArgumentInner::Char(self.value),
340 pad,
341 }
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use rand::{rngs::StdRng, Rng, SeedableRng};
348
349 use core::fmt::Alignment;
350 use std::string::ToString;
351
352 use super::*;
353
354 #[test]
355 fn length_estimation_for_small_ints() {
356 for i in 0_u8..=u8::MAX {
357 assert_eq!(
358 ArgumentWrapper::new(i).into_argument().formatted_len(),
359 i.to_string().len(),
360 "Formatted length estimated incorrectly for {i}"
361 );
362 }
363 for i in 0_u16..=u16::MAX {
364 assert_eq!(
365 ArgumentWrapper::new(i).into_argument().formatted_len(),
366 i.to_string().len(),
367 "Formatted length estimated incorrectly for {i}"
368 );
369 }
370 for i in i8::MIN..=i8::MAX {
371 assert_eq!(
372 ArgumentWrapper::new(i).into_argument().formatted_len(),
373 i.to_string().len(),
374 "Formatted length estimated incorrectly for {i}"
375 );
376 }
377 for i in i16::MIN..=i16::MAX {
378 assert_eq!(
379 ArgumentWrapper::new(i).into_argument().formatted_len(),
380 i.to_string().len(),
381 "Formatted length estimated incorrectly for {i}"
382 );
383 }
384 }
385
386 #[test]
387 fn length_estimation_for_large_ints() {
388 const RNG_SEED: u64 = 123;
389 const SAMPLE_COUNT: usize = 100_000;
390
391 let mut rng = StdRng::seed_from_u64(RNG_SEED);
392 for _ in 0..SAMPLE_COUNT {
393 let i: u32 = rng.gen();
394 assert_eq!(
395 ArgumentWrapper::new(i).into_argument().formatted_len(),
396 i.to_string().len(),
397 "Formatted length estimated incorrectly for {i}"
398 );
399 }
400 for _ in 0..SAMPLE_COUNT {
401 let i: u64 = rng.gen();
402 assert_eq!(
403 ArgumentWrapper::new(i).into_argument().formatted_len(),
404 i.to_string().len(),
405 "Formatted length estimated incorrectly for {i}"
406 );
407 }
408 for _ in 0..SAMPLE_COUNT {
409 let i: u128 = rng.gen();
410 assert_eq!(
411 ArgumentWrapper::new(i).into_argument().formatted_len(),
412 i.to_string().len(),
413 "Formatted length estimated incorrectly for {i}"
414 );
415 }
416 for _ in 0..SAMPLE_COUNT {
417 let i: usize = rng.gen();
418 assert_eq!(
419 ArgumentWrapper::new(i).into_argument().formatted_len(),
420 i.to_string().len(),
421 "Formatted length estimated incorrectly for {i}"
422 );
423 }
424
425 for _ in 0..SAMPLE_COUNT {
426 let i: i32 = rng.gen();
427 assert_eq!(
428 ArgumentWrapper::new(i).into_argument().formatted_len(),
429 i.to_string().len(),
430 "Formatted length estimated incorrectly for {i}"
431 );
432 }
433 for _ in 0..SAMPLE_COUNT {
434 let i: i64 = rng.gen();
435 assert_eq!(
436 ArgumentWrapper::new(i).into_argument().formatted_len(),
437 i.to_string().len(),
438 "Formatted length estimated incorrectly for {i}"
439 );
440 }
441 for _ in 0..SAMPLE_COUNT {
442 let i: i128 = rng.gen();
443 assert_eq!(
444 ArgumentWrapper::new(i).into_argument().formatted_len(),
445 i.to_string().len(),
446 "Formatted length estimated incorrectly for {i}"
447 );
448 }
449 for _ in 0..SAMPLE_COUNT {
450 let i: isize = rng.gen();
451 assert_eq!(
452 ArgumentWrapper::new(i).into_argument().formatted_len(),
453 i.to_string().len(),
454 "Formatted length estimated incorrectly for {i}"
455 );
456 }
457 }
458
459 #[test]
460 fn formatted_len_for_clipped_strings() {
461 let arg = ArgumentInner::Str(
462 "teßt",
463 Some(StrFormat {
464 clip_at: 2,
465 using: "",
466 }),
467 );
468 assert_eq!(arg.formatted_len(), StrLength::for_str("te"));
469
470 let arg = ArgumentInner::Str(
471 "teßt",
472 Some(StrFormat {
473 clip_at: 2,
474 using: "...",
475 }),
476 );
477 assert_eq!(arg.formatted_len(), StrLength::for_str("te..."));
478
479 let arg = ArgumentInner::Str(
480 "teßt",
481 Some(StrFormat {
482 clip_at: 2,
483 using: "…",
484 }),
485 );
486 assert_eq!(arg.formatted_len(), StrLength::for_str("te…"));
487
488 let arg = ArgumentInner::Str(
489 "teßt",
490 Some(StrFormat {
491 clip_at: 3,
492 using: "",
493 }),
494 );
495 assert_eq!(arg.formatted_len(), StrLength::for_str("teß"));
496
497 let arg = ArgumentInner::Str(
498 "teßt",
499 Some(StrFormat {
500 clip_at: 3,
501 using: "…",
502 }),
503 );
504 assert_eq!(arg.formatted_len(), StrLength::for_str("teß…"));
505
506 let arg = ArgumentInner::Str(
507 "teßt",
508 Some(StrFormat {
509 clip_at: 3,
510 using: "-",
511 }),
512 );
513 assert_eq!(arg.formatted_len(), StrLength::for_str("teß-"));
514
515 for clip_at in [4, 5, 16] {
516 for using in ["", "...", "…"] {
517 let arg = ArgumentInner::Str("teßt", Some(StrFormat { clip_at, using }));
518 assert_eq!(arg.formatted_len(), StrLength::for_str("teßt"));
519 }
520 }
521 }
522
523 #[test]
524 fn formatted_len_with_padding() {
525 let argument = Argument {
526 inner: ArgumentInner::Str("teßt", None),
527 pad: Some(Pad {
528 align: Alignment::Left,
529 width: 8,
530 using: ' ',
531 }),
532 };
533 assert_eq!(argument.formatted_len(), "teßt ".len());
534
535 let argument = Argument {
536 inner: ArgumentInner::Str("teßt", None),
537 pad: Some(Pad {
538 align: Alignment::Left,
539 width: 8,
540 using: '💣',
541 }),
542 };
543 assert_eq!(argument.formatted_len(), "teßt💣💣💣💣".len());
544
545 for pad_width in 1..=4 {
546 let argument = Argument {
547 inner: ArgumentInner::Str("teßt", None),
548 pad: Some(Pad {
549 align: Alignment::Left,
550 width: pad_width,
551 using: ' ',
552 }),
553 };
554 assert_eq!(argument.formatted_len(), "teßt".len());
555 }
556 }
557
558 #[test]
559 fn formatted_len_with_padding_and_clipping() {
560 let inner = ArgumentInner::Str(
561 "teßt",
562 Some(StrFormat {
563 clip_at: 3,
564 using: "…",
565 }),
566 );
567 let argument = Argument {
568 inner,
569 pad: Some(Pad {
570 align: Alignment::Left,
571 width: 8,
572 using: ' ',
573 }),
574 };
575 assert_eq!(argument.formatted_len(), "teß… ".len());
576
577 let argument = Argument {
578 inner,
579 pad: Some(Pad {
580 align: Alignment::Left,
581 width: 8,
582 using: '💣',
583 }),
584 };
585 assert_eq!(argument.formatted_len(), "teß…💣💣💣💣".len());
586
587 for pad_width in 1..=4 {
588 let argument = Argument {
589 inner,
590 pad: Some(Pad {
591 align: Alignment::Left,
592 width: pad_width,
593 using: ' ',
594 }),
595 };
596 assert_eq!(argument.formatted_len(), "teß…".len());
597 }
598 }
599
600 #[test]
601 #[should_panic(expected = "String 'teß…' contains non-ASCII chars; first at position 2")]
602 fn ascii_panic() {
603 Ascii::new("teß…");
604 }
605}