Add non-allocating formatted write implementation
This commit is contained in:
		
							parent
							
								
									f485629fb6
								
							
						
					
					
						commit
						cd16b733e9
					
				| 
						 | 
					@ -0,0 +1,107 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: BlueOak-1.0.0
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					// No-alloc write!() implementation from https://stackoverflow.com/a/50201632/145434
 | 
				
			||||||
 | 
					// Requires you to allocate a buffer somewhere manually.
 | 
				
			||||||
 | 
					// @todo Try to use arrayvec::ArrayString here instead?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use core::{cmp::min, fmt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct WriteTo<'a> {
 | 
				
			||||||
 | 
					    buffer: &'a mut [u8],
 | 
				
			||||||
 | 
					    // on write error (i.e. not enough space in buffer) this grows beyond
 | 
				
			||||||
 | 
					    // `buffer.len()`.
 | 
				
			||||||
 | 
					    used: usize,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> WriteTo<'a> {
 | 
				
			||||||
 | 
					    #[allow(unused)]
 | 
				
			||||||
 | 
					    pub fn new(buffer: &'a mut [u8]) -> Self {
 | 
				
			||||||
 | 
					        WriteTo { buffer, used: 0 }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[allow(unused)]
 | 
				
			||||||
 | 
					    pub fn into_str(self) -> Option<&'a str> {
 | 
				
			||||||
 | 
					        if self.used <= self.buffer.len() {
 | 
				
			||||||
 | 
					            // only successful concats of str - must be a valid str.
 | 
				
			||||||
 | 
					            Some(unsafe { core::str::from_utf8_unchecked(&self.buffer[..self.used]) })
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            None
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[allow(unused)]
 | 
				
			||||||
 | 
					    pub fn into_cstr(self) -> Option<&'a str> {
 | 
				
			||||||
 | 
					        if self.used < self.buffer.len() {
 | 
				
			||||||
 | 
					            self.buffer[self.used] = 0; // Terminate the string
 | 
				
			||||||
 | 
					            Some(unsafe { core::str::from_utf8_unchecked(&self.buffer[..=self.used]) })
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            None
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> fmt::Write for WriteTo<'a> {
 | 
				
			||||||
 | 
					    fn write_str(&mut self, s: &str) -> fmt::Result {
 | 
				
			||||||
 | 
					        if self.used > self.buffer.len() {
 | 
				
			||||||
 | 
					            return Err(fmt::Error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let remaining_buf = &mut self.buffer[self.used..];
 | 
				
			||||||
 | 
					        let raw_s = s.as_bytes();
 | 
				
			||||||
 | 
					        let write_num = min(raw_s.len(), remaining_buf.len());
 | 
				
			||||||
 | 
					        remaining_buf[..write_num].copy_from_slice(&raw_s[..write_num]);
 | 
				
			||||||
 | 
					        self.used += raw_s.len();
 | 
				
			||||||
 | 
					        if write_num < raw_s.len() {
 | 
				
			||||||
 | 
					            Err(fmt::Error)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Ok(())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[allow(unused)]
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					pub fn show<'a>(buffer: &'a mut [u8], args: fmt::Arguments) -> Result<&'a str, fmt::Error> {
 | 
				
			||||||
 | 
					    let mut w = WriteTo::new(buffer);
 | 
				
			||||||
 | 
					    fmt::write(&mut w, args)?;
 | 
				
			||||||
 | 
					    w.into_str().ok_or(fmt::Error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Return a zero-terminated str
 | 
				
			||||||
 | 
					#[allow(unused)]
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					// Crazy bug - result of this call disappears if it's not inlined (https://github.com/rust-lang/rust/issues/68812)
 | 
				
			||||||
 | 
					pub fn c_show<'a>(buffer: &'a mut [u8], args: fmt::Arguments) -> Result<&'a str, fmt::Error> {
 | 
				
			||||||
 | 
					    let mut w = WriteTo::new(buffer);
 | 
				
			||||||
 | 
					    fmt::write(&mut w, args)?;
 | 
				
			||||||
 | 
					    w.into_cstr().ok_or(fmt::Error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod tests {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test_case]
 | 
				
			||||||
 | 
					    pub fn write_to_works() {
 | 
				
			||||||
 | 
					        let mut buf = [0u8; 64];
 | 
				
			||||||
 | 
					        let s: &str = show(
 | 
				
			||||||
 | 
					            &mut buf,
 | 
				
			||||||
 | 
					            format_args!("write some stuff {:?}: {}", "foo", 42),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(s, "write some stuff \"foo\": 42");
 | 
				
			||||||
 | 
					        assert_eq!(s.as_ptr(), buf.as_ptr());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test_case]
 | 
				
			||||||
 | 
					    pub fn zero_terminated_write_to_works() {
 | 
				
			||||||
 | 
					        let mut buf = [0u8; 64];
 | 
				
			||||||
 | 
					        let s: &str = c_show(
 | 
				
			||||||
 | 
					            &mut buf,
 | 
				
			||||||
 | 
					            format_args!("write some stuff {:?}: {}", "foo", 42),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(s, "write some stuff \"foo\": 42\0");
 | 
				
			||||||
 | 
					        assert_eq!(s.as_ptr(), buf.as_ptr());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue