tfmt 0.4.0

A tiny, fast and panic-free alternative to `core::fmt`
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]

mod helpers;
mod impls;
mod utils;
use core::{slice::from_raw_parts, str::from_utf8_unchecked};

/// Derive macro
pub mod derive {
    pub use tfmt_macros::uDebug;
}

#[doc(hidden)]
pub use utils::{uDisplayFloat, uDisplayHex, UnstableDoAsFormatter};

/// This trait is used to write a message into a stream.
#[allow(non_camel_case_types)]
pub trait uWrite {
    /// The error associated to this writer
    type Error;

    /// Writes a string slice into this writer, returning whether the write succeeded
    ///
    /// This method can only succeed if the entire string slice was successfully written, and this
    /// method will not return until all data has been written or an error occurs.
    fn write_str(&mut self, s: &str) -> Result<(), Self::Error>;

    /// Writes a [`char`] into this writer, returning whether the write succeeded
    ///
    /// A single [`char`] may be encoded as more than one byte. This method can only succeed if the
    /// entire byte sequence was successfully written, and this method will not return until all
    /// data has been written or an error occurs.
    fn write_char(&mut self, c: char) -> Result<(), Self::Error> {
        let mut buf: [u8; 4] = [0; 4];
        self.write_str(c.encode_utf8(&mut buf))
    }
}

/// Creates a `String` using interpolation of runtime expressions.
///
/// The first argument specifies the length of the string generated by the macro.
/// The second argument `uformat!` is a format string. This must be a string
/// literal. The power of the formatting string is in the `{}`s contained.
/// Additional parameters passed to `format!` replace the `{}`s within the
/// formatting string in the order given unless positional parameters
/// are used. The macro returns [Result][core::result::Result].
///
/// See the formatting syntax documentation for details.
///
/// **Note**: In the no_std environment, [heapless::String] is used for string
/// representation. The length specification is used to scale this string. With
/// the `std` feature set, this is not actually necessary. However, the parameter
/// is also required here to ensure compatibility of the code.
///
/// ```
/// use tfmt::uformat;
///
/// assert_eq!(
///     uformat!(100, "The answer to {} is {}", "everything", 42).unwrap().as_str(),
///     "The answer to everything is 42"
/// );
/// ```
#[macro_export]
#[cfg(not(feature = "std"))]
macro_rules! uformat {
    ($cap:expr, $($tt:tt)*) => {{
        let mut s = heapless::String::<$cap>::new();
        #[allow(unreachable_code)]
        match tfmt::uwrite!(&mut s, $($tt)*) {
            Ok(_) => Ok(s),
            Err(e) => Err(e),
        }
    }};
}

/// Documentation
#[macro_export]
#[cfg(feature = "std")]
macro_rules! uformat {
    ($cap:expr, $($tt:tt)*) => {{
        let mut s = String::new();
        #[allow(unreachable_code)]
        match tfmt::uwrite!(&mut s, $($tt)*) {
            Ok(_) => Ok(s),
            Err(e) => Err(e),
        }
    }};
}

#[doc(hidden)]
#[macro_export]
macro_rules! udisplay_as_udebug {
    ($type: ty) => {
        impl uDebug for $type {
            #[inline(always)]
            fn fmt<W>(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error>
            where
                W: uWrite + ?Sized,
            {
                <$type as uDisplay>::fmt(self, f)
            }
        }
    };
}

/// Write formatted data into a buffer
///
/// This macro accepts a format string, a list of arguments, and a 'writer'. Arguments will be
/// formatted according to the specified format string and the result will be passed to the writer.
/// The writer must have type `[&mut] impl uWrite` or `[&mut] ufmt::Formatter<'_, impl uWrite>`. The
/// macro returns the associated `Error` type of the `uWrite`-r.
///
/// This is a procedural macro, which means that it is executed at compile time and the content is
/// interpreted then. The result is efficient executable code in the target system that does not
/// perform any interpretation or dynamic dispatch.
///
/// ```
/// use tfmt::uwrite;
///
/// let mut s = String::new();
/// uwrite!(&mut s, "the {} is {}", "number", 42).unwrap();
/// assert!(s.as_str() == "the number is 42");
/// ```
///
/// The syntax is similar to [`core::write!`]. The following patterns are supported:
///
/// | Pattern | Trait to be implemented | Remarks                                      |
/// |---------|-------------------------|----------------------------------------------|
/// | {}      | [uDisplay]              |                                              |
/// | {:8}    | [uDisplayPadded]        | pad_char: ' ', padding: Usual(8)             |
/// | {:08}   | [uDisplayPadded]        | pad_char: '0', padding: Usual(8)             |
/// | {:<8}   | [uDisplayPadded]        | pad_char: ' ', padding: LeftAligned(8)       |
/// | {:>8}   | [uDisplayPadded]        | pad_char: ' ', padding: RightAligned(8)      |
/// | {:^08}  | [uDisplayPadded]        | pad_char: '0', padding: CenterAligned(8)     |
/// | {:8a2}  | [uDisplayFormatted]     | padding: Usual(8), cmd: 'a', behind: 2       |
/// | {:08a2} | [uDisplayFormatted]     | pad_char: '0', for the rest see above        |
/// | {:#8a2} | [uDisplayFormatted]     | prefix: true, for the rest see above         |
/// | {:.2}   | internal float          | padding: Usual(0), behind: 2                 |
/// | {:8.2}  | internal float          | padding: Usual(8), behind: 2                 |
/// | {:08.2} | internal float          | pad_char: '0', for the rest see above        |
/// | {:x}    | internal hex            | padding: Usual(0)                            |
/// | {:8x}   | internal hex            | pad_char: ' ', padding: Usual(8)             |
/// | {:08x}  | internal hex            | pad_char: '0', padding: Usual(8)             |
/// | {:08x}  | internal hex            | pad_char: '0', padding: Usual(8)             |
/// | {:#x}   | internal hex            | prefix: true                                 |
/// | {{, }}  | -                       | escape braces                                |
///
/// For more details see:
/// - integer formatting: `tests/int.rs`
/// - float formatting: `tests/float.rs`
/// - string and char formatting: `tests/core.rs`
/// - [uDisplayFormatted] example: `examples/coords`
///
#[cfg(not(doctest))] // only ok with features "std"
pub use tfmt_macros::uwrite;

/// Write formatted data into a buffer, with a newline appended
///
/// See [`uwrite!`](macro.uwrite.html) for more details
pub use tfmt_macros::uwriteln;

/// Configuration for formatting
#[allow(non_camel_case_types)]
pub struct Formatter<'w, W>
where
    W: uWrite + ?Sized,
{
    writer: &'w mut W,
    indentation: usize,
    pretty: bool,
}

impl<'w, W> Formatter<'w, W>
where
    W: uWrite + ?Sized,
{
    /// Creates a formatter from the given writer
    pub fn new(writer: &'w mut W) -> Self {
        Self {
            writer,
            indentation: 0,
            pretty: false,
        }
    }

    /// Writes a character to the underlying buffer
    pub fn write_char(&mut self, c: char) -> Result<(), W::Error> {
        let mut buf = [0_u8; 4];
        let s = c.encode_utf8(&mut buf);
        self.writer.write_str(s)
    }

    /// Writes a string slice to the underlying buffer
    pub fn write_str(&mut self, s: &str) -> Result<(), W::Error> {
        self.writer.write_str(s)
    }

    /// Execute the closure with pretty-printing enabled
    pub fn pretty(
        &mut self,
        f: impl FnOnce(&mut Self) -> Result<(), W::Error>,
    ) -> Result<(), W::Error> {
        let pretty = self.pretty;
        self.pretty = true;
        f(self)?;
        self.pretty = pretty;
        Ok(())
    }

    /// Write whitespace according to the current `self.indentation`
    fn indent(&mut self) -> Result<(), W::Error> {
        for _ in 0..self.indentation {
            self.write_str("    ")?;
        }

        Ok(())
    }

    /// Writes a string slice to the underlying buffer and fills it with the pad_char according to
    /// the padding specifications. Here, `Padding::Usual` is treated in the same way as
    /// `Padding::RightAligned`.
    pub fn write_padded(
        &mut self,
        s: &str,
        pad_char: char,
        padding: Padding,
    ) -> Result<(), W::Error> {
        // Converting a char to &str is expensive, so we only do it once
        let mut buf = [0_u8; 4];
        let pad_c = pad_char.encode_utf8(&mut buf);
        match padding {
            Padding::LeftAligned(pad_length) => {
                self.writer.write_str(s)?;
                for _ in s.len()..pad_length {
                    self.writer.write_str(pad_c)?;
                }
                Ok(())
            }
            Padding::Usual(pad_length) | Padding::RightAligned(pad_length) => {
                for _ in s.len()..pad_length {
                    self.writer.write_str(pad_c)?;
                }
                self.writer.write_str(s)
            }
            Padding::CenterAligned(pad_length) => {
                let padding = pad_length - s.len();
                let half = padding / 2;
                for _ in 0..half {
                    self.writer.write_str(pad_c)?;
                }
                self.writer.write_str(s)?;
                for _ in half..padding {
                    self.writer.write_str(pad_c)?;
                }
                Ok(())
            }
        }
    }
}

/// Implement this trait if `{}` is to be used with the write macro.
///
/// See [uwrite] for details
#[allow(non_camel_case_types)]
pub trait uDisplay {
    /// Formats the value using the given formatter
    fn fmt<W>(&self, _: &mut Formatter<'_, W>) -> Result<(), W::Error>
    where
        W: uWrite + ?Sized;
}

/// Just like `core::fmt::Debug`
#[allow(non_camel_case_types)]
pub trait uDebug {
    /// Formats the value using the given formatter
    fn fmt<W>(&self, _: &mut Formatter<'_, W>) -> Result<(), W::Error>
    where
        W: uWrite + ?Sized;
}

/// This enum determines how the display is to be filled, see [uwrite] for more details.
#[derive(PartialEq, Clone, Copy)]
pub enum Padding {
    /// Usual padding left or right depending on type
    Usual(usize),
    /// Padding left aligned
    LeftAligned(usize),
    /// Padding right aligned
    RightAligned(usize),
    /// Padding on left and right side
    CenterAligned(usize),
}

/// Creating padded output string
///
/// See [uwrite] for details.
/// 
/// ```
/// use tfmt::{uformat, uDisplayPadded, Convert};
/// 
/// struct Time {
///     hour: u8,
///     min: u8,
///     sec: u8,
/// }
/// 
/// impl uDisplayPadded for Time {
///     fn fmt_padded<W>(
///             &self,
///             fmt: &mut tfmt::Formatter<'_, W>,
///             padding: tfmt::Padding,
///             pad_char: char,
///         ) -> Result<(), W::Error>
///         where
///             W: tfmt::uWrite + ?Sized 
///     {
///         let mut conv = Convert::<6>::new(b'0');
///         conv.u32_pad(self.sec as u32, 2).unwrap();
///         conv.u32_pad(self.min as u32, 2).unwrap();
///         conv.u32_pad(self.hour as u32, 2).unwrap();
///         fmt.write_padded(conv.as_str(), pad_char, padding)
///     }
/// }
/// 
/// let time = Time { hour: 3, min: 17, sec: 7 };
/// let s = uformat!(100, "{:^10}", time).unwrap();
/// assert_eq!("  031707  ", s.as_str());
/// ```
#[allow(non_camel_case_types)]
pub trait uDisplayPadded {
    /// Formats the value using the given formatter
    fn fmt_padded<W>(
        &self,
        _: &mut Formatter<'_, W>,
        padding: Padding,
        pad_char: char,
    ) -> Result<(), W::Error>
    where
        W: uWrite + ?Sized;
}

/// Creating formatted output string
///
/// See [uwrite] for details
/// 
/// ```
/// use std::f64::consts::PI;
/// use tfmt::{uDisplayFormatted, uformat, Convert};
///
/// struct Coord(f64);
///
/// impl uDisplayFormatted for Coord {
///     fn fmt_formatted<W>(
///         &self,
///         fmt: &mut tfmt::Formatter<'_, W>,
///         _prefix: bool,
///         cmd: char,
///         padding: tfmt::Padding,
///         pad_char: char,
///         decimal_places: usize,
///     ) -> Result<(), W::Error>
///     where
///         W: tfmt::uWrite + ?Sized,
///     {
///         let (sign, rad) = match cmd {
///             'E' => {
///                 if (*self).0.is_sign_positive() {
///                     (b'E', (*self).0)
///                 } else {
///                     (b'W', -(*self).0)
///                 }
///             }
///             _ => {
///                 if (*self).0.is_sign_positive() {
///                     (b'N', (*self).0)
///                 } else {
///                     (b'S', -(*self).0)
///                 }
///             }
///         };

///         let degs = rad * 180.0 / PI;
///         let mins = degs.fract() * 60.0;

///         let l_min = if decimal_places > 0 {
///             decimal_places + 3
///         } else {
///             decimal_places + 2
///         };

///         let mut conv = Convert::<15>::new(b'0');
///         conv.write_u8(sign).unwrap();
///         conv.write_u8(b',').unwrap();
///         conv.f64_pad(mins, l_min, decimal_places).unwrap();
///         conv.u32(degs as u32).unwrap();
///         fmt.write_padded(conv.as_str(), pad_char, padding)
///     }
/// }

/// let lat_berlin = Coord(0.9180516165333352);
/// let lon_berlin = Coord(0.23304198843966833);

/// /// format for coord is dddmm
/// let s = uformat!(100, "{:N0},{:E0}", lat_berlin, lon_berlin).unwrap();
/// assert_eq!("5236,N,1321,E", s.as_str());

/// /// format for coord is dddmm.mmm
/// let s = uformat!(100, "{:N3},{:E3}", lat_berlin, lon_berlin).unwrap();
/// assert_eq!("5236.029,N,1321.139,E", s.as_str());

/// /// format for coord is dddmm.mmmmmm
/// let s = uformat!(100, "{:013N6},{:014E6}", lat_berlin, lon_berlin).unwrap();
/// assert_eq!("5236.028980,N,01321.139343,E", s.as_str());
/// ```
#[allow(non_camel_case_types)]
pub trait uDisplayFormatted {
    /// Formats the value using the given formatter
    fn fmt_formatted<W>(
        &self,
        _: &mut Formatter<'_, W>,
        prefix: bool,
        cmd: char,
        padding: Padding,
        pad_char: char,
        behind: usize,
    ) -> Result<(), W::Error>
    where
        W: uWrite + ?Sized;
}

/// Converts numerical data types to &str
///
/// Convert contains a little public toolbox to convert numerical data to strings. So You can
/// integrate your own data types easily and efficiently.
///
/// You will only need this component eventually if you implement yourself the [uDisplay],
/// [uDisplayPadded] or the [uDisplayFormatted] trait of this crate.
pub struct Convert<const CAP: usize> {
    buf: [u8; CAP],
    idx: usize,
}

impl<const CAP: usize> Convert<CAP> {
    /// Creates a new Convert instance with buffer set to pad_char
    pub fn new(pad_char: u8) -> Convert<CAP> {
        Convert { buf: [pad_char; CAP], idx: CAP }
    }

    /// Returns a reference to the string contained in Convert
    pub fn as_str(&self) -> &str {
        // SAFETY: We only return characters here that we have previously initialised. This is
        // therefore safe and a new check for utf8 conformity is pointless.
        unsafe {
            let p_buf = self.buf.as_ptr().cast::<u8>();
            let length = CAP - self.idx;
            let slice = from_raw_parts(p_buf.add(self.idx), length);
            from_utf8_unchecked(slice)
        }
    }

    /// Writes a u8 to the buffer and post decrements the idx
    pub fn write_u8(&mut self, c: u8) -> Result<(), ()> {
        if self.idx > 0 {
            let p_buf = self.buf.as_mut_ptr().cast::<u8>();
            self.idx -= 1;
            // SAFETY: Since idx >= 0 and below CAP, this access is secure. This construct is
            // necessary because rust array accesses generate an undesired panicking branch.
            unsafe { p_buf.add(self.idx).write_volatile(c) };
            Ok(())
        } else {
            Err(())
        }
    }

    /// Writes a string to the buffer
    pub fn write_str(&mut self, s: &str) -> Result<(), ()> {
        for c in s.bytes().rev() {
            self.write_u8(c)?;
        }
        Ok(())
    }
}