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}