fish_printf/
arg.rs

1use super::printf_impl::Error;
2use std::result::Result;
3#[cfg(feature = "widestring")]
4use widestring::{Utf32Str as wstr, Utf32String as WString};
5
6/// Printf argument types.
7/// Note no implementation of `ToArg` constructs the owned variants (String and WString);
8/// callers can do so explicitly.
9#[derive(Debug, PartialEq)]
10pub enum Arg<'a> {
11    Str(&'a str),
12    #[cfg(feature = "widestring")]
13    WStr(&'a wstr),
14    String(String),
15    #[cfg(feature = "widestring")]
16    WString(WString),
17    UInt(u64),
18    SInt(i64, u8), // signed integers track their width as the number of bits
19    Float(f64),
20    USizeRef(&'a mut usize), // for use with %n
21}
22
23impl<'a> Arg<'a> {
24    pub fn set_count(&mut self, count: usize) -> Result<(), Error> {
25        match self {
26            Arg::USizeRef(p) => **p = count,
27            _ => return Err(Error::BadArgType),
28        }
29        Ok(())
30    }
31
32    // Convert this to a narrow string, using the provided storage if necessary.
33    // In practice 'storage' is only used if the widestring feature is enabled.
34    #[allow(unused_variables, clippy::ptr_arg)]
35    pub fn as_str<'s>(&'s self, storage: &'s mut String) -> Result<&'s str, Error>
36    where
37        'a: 's,
38    {
39        match self {
40            Arg::Str(s) => Ok(s),
41            Arg::String(s) => Ok(s),
42            #[cfg(feature = "widestring")]
43            Arg::WStr(s) => {
44                storage.clear();
45                storage.extend(s.chars());
46                Ok(storage)
47            }
48            #[cfg(feature = "widestring")]
49            Arg::WString(s) => {
50                storage.clear();
51                storage.extend(s.chars());
52                Ok(storage)
53            }
54            _ => Err(Error::BadArgType),
55        }
56    }
57
58    // Return this value as an unsigned integer. Negative signed values will report overflow.
59    pub fn as_uint(&self) -> Result<u64, Error> {
60        match *self {
61            Arg::UInt(u) => Ok(u),
62            Arg::SInt(i, _w) => i.try_into().map_err(|_| Error::Overflow),
63            _ => Err(Error::BadArgType),
64        }
65    }
66
67    // Return this value as a signed integer. Unsigned values > i64::MAX will report overflow.
68    pub fn as_sint(&self) -> Result<i64, Error> {
69        match *self {
70            Arg::UInt(u) => u.try_into().map_err(|_| Error::Overflow),
71            Arg::SInt(i, _w) => Ok(i),
72            _ => Err(Error::BadArgType),
73        }
74    }
75
76    // If this is a signed value, then return the sign (true if negative) and the magnitude,
77    // masked to the value's width. This allows for e.g. -1 to be returned as 0xFF, 0xFFFF, etc.
78    // depending on the original width.
79    // If this is an unsigned value, simply return (false, u64).
80    pub fn as_wrapping_sint(&self) -> Result<(bool, u64), Error> {
81        match *self {
82            Arg::UInt(u) => Ok((false, u)),
83            Arg::SInt(i, w) => {
84                // Need to shift twice in case w is 64.
85                debug_assert!(w > 0);
86                let mask = ((1u64 << (w - 1)) << 1).wrapping_sub(1);
87                let ui = (i as u64) & mask;
88                Ok((i < 0, ui))
89            }
90            _ => Err(Error::BadArgType),
91        }
92    }
93
94    // Note we allow passing ints as floats, even allowing precision loss.
95    pub fn as_float(&self) -> Result<f64, Error> {
96        #[allow(clippy::cast_precision_loss)]
97        match *self {
98            Arg::Float(f) => Ok(f),
99            Arg::UInt(u) => Ok(u as f64),
100            Arg::SInt(i, _w) => Ok(i as f64),
101            _ => Err(Error::BadArgType),
102        }
103    }
104
105    pub fn as_char(&self) -> Result<char, Error> {
106        let v: u32 = self.as_uint()?.try_into().map_err(|_| Error::Overflow)?;
107        v.try_into().map_err(|_| Error::Overflow)
108    }
109}
110
111/// Conversion from a raw value to a printf argument.
112pub trait ToArg<'a> {
113    fn to_arg(self) -> Arg<'a>;
114}
115
116impl<'a> ToArg<'a> for &'a str {
117    fn to_arg(self) -> Arg<'a> {
118        Arg::Str(self)
119    }
120}
121
122impl<'a> ToArg<'a> for &'a String {
123    fn to_arg(self) -> Arg<'a> {
124        Arg::Str(self)
125    }
126}
127
128#[cfg(feature = "widestring")]
129impl<'a> ToArg<'a> for &'a wstr {
130    fn to_arg(self) -> Arg<'a> {
131        Arg::WStr(self)
132    }
133}
134
135#[cfg(feature = "widestring")]
136impl<'a> ToArg<'a> for &'a WString {
137    fn to_arg(self) -> Arg<'a> {
138        Arg::WStr(self)
139    }
140}
141
142impl<'a> ToArg<'a> for f32 {
143    fn to_arg(self) -> Arg<'a> {
144        Arg::Float(self.into())
145    }
146}
147
148impl<'a> ToArg<'a> for f64 {
149    fn to_arg(self) -> Arg<'a> {
150        Arg::Float(self)
151    }
152}
153
154impl<'a> ToArg<'a> for char {
155    fn to_arg(self) -> Arg<'a> {
156        Arg::UInt((self as u32).into())
157    }
158}
159
160impl<'a> ToArg<'a> for &'a mut usize {
161    fn to_arg(self) -> Arg<'a> {
162        Arg::USizeRef(self)
163    }
164}
165
166impl<'a, T> ToArg<'a> for &'a *const T {
167    fn to_arg(self) -> Arg<'a> {
168        Arg::UInt((*self) as usize as u64)
169    }
170}
171
172/// All signed types.
173macro_rules! impl_to_arg {
174    ($($t:ty),*) => {
175        $(
176            impl<'a> ToArg<'a> for $t {
177                fn to_arg(self) -> Arg<'a> {
178                    Arg::SInt(self as i64, <$t>::BITS as u8)
179                }
180            }
181        )*
182    };
183}
184impl_to_arg!(i8, i16, i32, i64, isize);
185
186/// All unsigned types.
187macro_rules! impl_to_arg_u {
188    ($($t:ty),*) => {
189        $(
190            impl<'a> ToArg<'a> for $t {
191                fn to_arg(self) -> Arg<'a> {
192                    Arg::UInt(self as u64)
193                }
194            }
195        )*
196    };
197}
198impl_to_arg_u!(u8, u16, u32, u64, usize);
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    #[cfg(feature = "widestring")]
204    use widestring::utf32str;
205
206    #[test]
207    fn test_to_arg() {
208        const SIZE_WIDTH: u8 = isize::BITS as u8;
209
210        assert!(matches!("test".to_arg(), Arg::Str("test")));
211        assert!(matches!(String::from("test").to_arg(), Arg::Str(_)));
212        #[cfg(feature = "widestring")]
213        assert!(matches!(utf32str!("test").to_arg(), Arg::WStr(_)));
214        #[cfg(feature = "widestring")]
215        assert!(matches!(WString::from("test").to_arg(), Arg::WStr(_)));
216        assert!(matches!(42f32.to_arg(), Arg::Float(_)));
217        assert!(matches!(42f64.to_arg(), Arg::Float(_)));
218        assert!(matches!('x'.to_arg(), Arg::UInt(120)));
219        let mut usize_val: usize = 0;
220        assert!(matches!((&mut usize_val).to_arg(), Arg::USizeRef(_)));
221        assert!(matches!(42i8.to_arg(), Arg::SInt(42, 8)));
222        assert!(matches!(42i16.to_arg(), Arg::SInt(42, 16)));
223        assert!(matches!(42i32.to_arg(), Arg::SInt(42, 32)));
224        assert!(matches!(42i64.to_arg(), Arg::SInt(42, 64)));
225        assert!(matches!(42isize.to_arg(), Arg::SInt(42, SIZE_WIDTH)));
226
227        assert_eq!((-42i8).to_arg(), Arg::SInt(-42, 8));
228        assert_eq!((-42i16).to_arg(), Arg::SInt(-42, 16));
229        assert_eq!((-42i32).to_arg(), Arg::SInt(-42, 32));
230        assert_eq!((-42i64).to_arg(), Arg::SInt(-42, 64));
231        assert_eq!((-42isize).to_arg(), Arg::SInt(-42, SIZE_WIDTH));
232
233        assert!(matches!(42u8.to_arg(), Arg::UInt(42)));
234        assert!(matches!(42u16.to_arg(), Arg::UInt(42)));
235        assert!(matches!(42u32.to_arg(), Arg::UInt(42)));
236        assert!(matches!(42u64.to_arg(), Arg::UInt(42)));
237        assert!(matches!(42usize.to_arg(), Arg::UInt(42)));
238
239        let ptr = &42f32 as *const f32;
240        assert!(matches!(ptr.to_arg(), Arg::UInt(_)));
241    }
242
243    #[test]
244    fn test_negative_to_arg() {
245        assert_eq!((-1_i8).to_arg().as_sint(), Ok(-1));
246        assert_eq!((-1_i16).to_arg().as_sint(), Ok(-1));
247        assert_eq!((-1_i32).to_arg().as_sint(), Ok(-1));
248        assert_eq!((-1_i64).to_arg().as_sint(), Ok(-1));
249
250        assert_eq!((u64::MAX).to_arg().as_sint(), Err(Error::Overflow));
251    }
252}