sprintf/
lib.rs

1//! Libc s(n)printf clone written in Rust, so you can use printf-style
2//! formatting without a libc (e.g. in WebAssembly).
3//!
4//! **Note:** *You're probably better off using standard Rust string formatting
5//! instead of thie crate unless you specificaly need printf compatibility.*
6//!
7//! It follows the standard C semantics, except:
8//!
9//!  * Locale-aware UNIX extensions (`'` and GNU’s `I`) are not supported.
10//!  * `%a`/`%A` (hexadecimal floating point) are currently not implemented.
11//!  * Length modifiers (`h`, `l`, etc.) are checked, but ignored. The passed
12//!    type is used instead.
13//!
14//! Usage example:
15//!
16//!     use sprintf::sprintf;
17//!     let s = sprintf!("%d + %d = %d\n", 3, 9, 3+9).unwrap();
18//!     assert_eq!(s, "3 + 9 = 12\n");
19//!
20//! The types of the arguments are checked at runtime.
21//!
22
23use thiserror::Error;
24
25mod format;
26pub mod parser;
27
28pub use format::Printf;
29use parser::{parse_format_string, FormatElement};
30#[doc(hidden)]
31pub use parser::{ConversionSpecifier, ConversionType, NumericParam};
32
33/// Error type
34#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
35pub enum PrintfError {
36    /// Error parsing the format string
37    #[error("Error parsing the format string")]
38    ParseError,
39    /// Incorrect type passed as an argument
40    #[error("Incorrect type passed as an argument")]
41    WrongType,
42    /// Too many arguments passed
43    #[error("Too many arguments passed")]
44    TooManyArgs,
45    /// Too few arguments passed
46    #[error("Too few arguments passed")]
47    NotEnoughArgs,
48    /// Other error (should never happen)
49    #[error("Other error (should never happen)")]
50    Unknown,
51}
52
53pub type Result<T> = std::result::Result<T, PrintfError>;
54
55/// Format a string. (Roughly equivalent to `vsnprintf` or `vasprintf` in C)
56///
57/// Takes a printf-style format string `format` and a slice of dynamically
58/// typed arguments, `args`.
59///
60///     use sprintf::{vsprintf, Printf};
61///     let n = 16;
62///     let args: Vec<&dyn Printf> = vec![&n];
63///     let s = vsprintf("%#06x", &args).unwrap();
64///     assert_eq!(s, "0x0010");
65///
66/// See also: [sprintf]
67pub fn vsprintf(format: &str, args: &[&dyn Printf]) -> Result<String> {
68    vsprintfp(&parse_format_string(format)?, args)
69}
70
71/// Format a string using [`parser::FormatElement`]s
72///
73/// Like [vsprintf], except that it doesn't parse the format string.
74pub fn vsprintfp(format: &[FormatElement], args: &[&dyn Printf]) -> Result<String> {
75    let mut res = String::new();
76
77    let mut args = args;
78    let mut pop_arg = || {
79        if args.is_empty() {
80            Err(PrintfError::NotEnoughArgs)
81        } else {
82            let a = args[0];
83            args = &args[1..];
84            Ok(a)
85        }
86    };
87
88    for elem in format {
89        match elem {
90            FormatElement::Verbatim(s) => {
91                res.push_str(s);
92            }
93            FormatElement::Format(spec) => {
94                if spec.conversion_type == ConversionType::PercentSign {
95                    res.push('%');
96                } else {
97                    let mut completed_spec = *spec;
98                    if spec.width == NumericParam::FromArgument {
99                        completed_spec.width = NumericParam::Literal(
100                            pop_arg()?.as_int().ok_or(PrintfError::WrongType)?,
101                        )
102                    }
103                    if spec.precision == NumericParam::FromArgument {
104                        completed_spec.precision = NumericParam::Literal(
105                            pop_arg()?.as_int().ok_or(PrintfError::WrongType)?,
106                        )
107                    }
108                    res.push_str(&pop_arg()?.format(&completed_spec)?);
109                }
110            }
111        }
112    }
113
114    if args.is_empty() {
115        Ok(res)
116    } else {
117        Err(PrintfError::TooManyArgs)
118    }
119}
120
121/// Format a string. (Roughly equivalent to `snprintf` or `asprintf` in C)
122///
123/// Takes a printf-style format string `format` and a variable number of
124/// additional arguments.
125///
126///     use sprintf::sprintf;
127///     let s = sprintf!("%s = %*d", "forty-two", 4, 42).unwrap();
128///     assert_eq!(s, "forty-two =   42");
129///
130/// Wrapper around [vsprintf].
131#[macro_export]
132macro_rules! sprintf {
133    ($fmt:expr, $($arg:expr),*) => {
134        sprintf::vsprintf($fmt, &[$( &($arg) as &dyn sprintf::Printf),* ][..])
135    };
136}