Skip to main content

luau_printf/
arg.rs

1use super::printf_impl::Error;
2use bstr::{BStr, BString, ByteSlice};
3use std::result::Result;
4
5/// Printf argument types.
6/// Note no implementation of `ToArg` constructs the owned string variant;
7/// callers can do so explicitly.
8#[derive(Debug, PartialEq)]
9pub enum Arg<'a> {
10    Str(&'a BStr),
11    String(BString),
12    UInt(u64),
13    SInt(i64),
14    Float(f64),
15    USizeRef(&'a mut usize), // for use with %n
16}
17
18impl<'a> Arg<'a> {
19    pub fn string(bytes: &'a BStr) -> Self {
20        Self::Str(bytes)
21    }
22
23    pub fn int(value: i32) -> Self {
24        Self::SInt(i64::from(value))
25    }
26
27    pub fn sint(value: i64) -> Self {
28        Self::SInt(value)
29    }
30
31    pub fn hex(value: u32) -> Self {
32        Self::UInt(u64::from(value))
33    }
34
35    pub fn uint(value: u64) -> Self {
36        Self::UInt(value)
37    }
38
39    pub fn float(value: f64) -> Self {
40        Self::Float(value)
41    }
42
43    pub fn pointer(value: usize) -> Self {
44        Self::UInt(value as u64)
45    }
46
47    pub fn set_count(&mut self, count: usize) -> Result<(), Error> {
48        match self {
49            Arg::USizeRef(p) => **p = count,
50            _ => return Err(Error::BadArgType),
51        }
52        Ok(())
53    }
54
55    pub fn as_bstr(&self) -> Result<&BStr, Error> {
56        match self {
57            Arg::Str(s) => Ok(s),
58            Arg::String(s) => Ok(s.as_bstr()),
59            _ => Err(Error::BadArgType),
60        }
61    }
62
63    // Return this value as an unsigned integer. Negative signed values will report overflow.
64    pub fn as_uint(&self) -> Result<u64, Error> {
65        match *self {
66            Arg::UInt(u) => Ok(u),
67            Arg::SInt(i) => i.try_into().map_err(|_| Error::Overflow),
68            _ => Err(Error::BadArgType),
69        }
70    }
71
72    // Return this value as a signed integer. Unsigned values > i64::MAX will report overflow.
73    pub fn as_sint(&self) -> Result<i64, Error> {
74        match *self {
75            Arg::UInt(u) => u.try_into().map_err(|_| Error::Overflow),
76            Arg::SInt(i) => Ok(i),
77            _ => Err(Error::BadArgType),
78        }
79    }
80
81    /// Unwraps [`Arg::UInt`] to [`u64`].
82    /// Unwraps [`Arg::SInt`] and casts the [`i64`] to [`u64`].
83    /// Calling this on other variants of `[Arg]` is an error.
84    pub fn as_wrapping_sint(&self) -> Result<u64, Error> {
85        match *self {
86            Arg::UInt(u) => Ok(u),
87            Arg::SInt(i) => Ok(i as u64),
88            _ => Err(Error::BadArgType),
89        }
90    }
91
92    // Note we allow passing ints as floats, even allowing precision loss.
93    pub fn as_float(&self) -> Result<f64, Error> {
94        #[allow(clippy::cast_precision_loss)]
95        match *self {
96            Arg::Float(f) => Ok(f),
97            Arg::UInt(u) => Ok(u as f64),
98            Arg::SInt(i) => Ok(i as f64),
99            _ => Err(Error::BadArgType),
100        }
101    }
102
103    pub fn as_uchar(&self) -> Result<u8, Error> {
104        Ok(self.as_wrapping_sint()? as u8)
105    }
106}
107
108/// Conversion from a raw value to a printf argument.
109pub trait ToArg<'a> {
110    fn to_arg(self) -> Arg<'a>;
111}
112
113impl<'a> ToArg<'a> for &'a str {
114    fn to_arg(self) -> Arg<'a> {
115        Arg::Str(self.as_bytes().as_bstr())
116    }
117}
118
119impl<'a> ToArg<'a> for &'a String {
120    fn to_arg(self) -> Arg<'a> {
121        Arg::Str(self.as_bytes().as_bstr())
122    }
123}
124
125impl<'a> ToArg<'a> for String {
126    fn to_arg(self) -> Arg<'a> {
127        Arg::String(BString::from(self))
128    }
129}
130
131impl<'a> ToArg<'a> for &'a BStr {
132    fn to_arg(self) -> Arg<'a> {
133        Arg::Str(self)
134    }
135}
136
137impl<'a> ToArg<'a> for &'a BString {
138    fn to_arg(self) -> Arg<'a> {
139        Arg::Str(self.as_bstr())
140    }
141}
142
143impl<'a> ToArg<'a> for BString {
144    fn to_arg(self) -> Arg<'a> {
145        Arg::String(self)
146    }
147}
148
149impl<'a> ToArg<'a> for &'a [u8] {
150    fn to_arg(self) -> Arg<'a> {
151        Arg::Str(self.as_bstr())
152    }
153}
154
155impl<'a> ToArg<'a> for &'a Vec<u8> {
156    fn to_arg(self) -> Arg<'a> {
157        Arg::Str(self.as_slice().as_bstr())
158    }
159}
160
161impl<'a> ToArg<'a> for Vec<u8> {
162    fn to_arg(self) -> Arg<'a> {
163        Arg::String(BString::from(self))
164    }
165}
166
167impl<'a, const N: usize> ToArg<'a> for &'a [u8; N] {
168    fn to_arg(self) -> Arg<'a> {
169        Arg::Str(self.as_bstr())
170    }
171}
172
173impl<'a> ToArg<'a> for Arg<'a> {
174    fn to_arg(self) -> Arg<'a> {
175        self
176    }
177}
178
179impl<'a> ToArg<'a> for &'a std::io::Error {
180    fn to_arg(self) -> Arg<'a> {
181        Arg::String(BString::from(self.to_string()))
182    }
183}
184
185impl<'a> ToArg<'a> for f32 {
186    fn to_arg(self) -> Arg<'a> {
187        Arg::Float(self.into())
188    }
189}
190
191impl<'a> ToArg<'a> for f64 {
192    fn to_arg(self) -> Arg<'a> {
193        Arg::Float(self)
194    }
195}
196
197impl<'a> ToArg<'a> for &'a mut usize {
198    fn to_arg(self) -> Arg<'a> {
199        Arg::USizeRef(self)
200    }
201}
202
203impl<'a, T> ToArg<'a> for &'a *const T {
204    fn to_arg(self) -> Arg<'a> {
205        Arg::UInt((*self) as usize as u64)
206    }
207}
208
209/// All signed types.
210macro_rules! impl_to_arg {
211    ($($t:ty),*) => {
212        $(
213            impl<'a> ToArg<'a> for $t {
214                fn to_arg(self) -> Arg<'a> {
215                    Arg::SInt(self as i64)
216                }
217            }
218        )*
219    };
220}
221impl_to_arg!(i8, i16, i32, i64, isize);
222
223/// All unsigned types.
224macro_rules! impl_to_arg_u {
225    ($($t:ty),*) => {
226        $(
227            impl<'a> ToArg<'a> for $t {
228                fn to_arg(self) -> Arg<'a> {
229                    Arg::UInt(self as u64)
230                }
231            }
232        )*
233    };
234}
235impl_to_arg_u!(u8, u16, u32, u64, usize);
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_to_arg() {
243        assert!(matches!("test".to_arg(), Arg::Str(_)));
244        assert!(matches!((&String::from("test")).to_arg(), Arg::Str(_)));
245        assert!(matches!(String::from("test").to_arg(), Arg::String(_)));
246        assert!(matches!(b"test".to_arg(), Arg::Str(_)));
247        assert!(matches!(b"test".as_slice().to_arg(), Arg::Str(_)));
248        assert!(matches!((&BString::from("test")).to_arg(), Arg::Str(_)));
249        assert!(matches!(BString::from("test").to_arg(), Arg::String(_)));
250        assert!(matches!((&b"test".to_vec()).to_arg(), Arg::Str(_)));
251        assert!(matches!(b"test".to_vec().to_arg(), Arg::String(_)));
252        assert!(matches!(42f32.to_arg(), Arg::Float(_)));
253        assert!(matches!(42f64.to_arg(), Arg::Float(_)));
254        let mut usize_val: usize = 0;
255        assert!(matches!((&mut usize_val).to_arg(), Arg::USizeRef(_)));
256        assert!(matches!(42i8.to_arg(), Arg::SInt(42)));
257        assert!(matches!(42i16.to_arg(), Arg::SInt(42)));
258        assert!(matches!(42i32.to_arg(), Arg::SInt(42)));
259        assert!(matches!(42i64.to_arg(), Arg::SInt(42)));
260        assert!(matches!(42isize.to_arg(), Arg::SInt(42)));
261
262        assert_eq!((-42i8).to_arg(), Arg::SInt(-42));
263        assert_eq!((-42i16).to_arg(), Arg::SInt(-42));
264        assert_eq!((-42i32).to_arg(), Arg::SInt(-42));
265        assert_eq!((-42i64).to_arg(), Arg::SInt(-42));
266        assert_eq!((-42isize).to_arg(), Arg::SInt(-42));
267
268        assert!(matches!(42u8.to_arg(), Arg::UInt(42)));
269        assert!(matches!(42u16.to_arg(), Arg::UInt(42)));
270        assert!(matches!(42u32.to_arg(), Arg::UInt(42)));
271        assert!(matches!(42u64.to_arg(), Arg::UInt(42)));
272        assert!(matches!(42usize.to_arg(), Arg::UInt(42)));
273
274        let ptr = std::ptr::from_ref(&42f32);
275        assert!(matches!(ptr.to_arg(), Arg::UInt(_)));
276    }
277
278    #[test]
279    fn test_negative_to_arg() {
280        assert_eq!((-1_i8).to_arg().as_sint(), Ok(-1));
281        assert_eq!((-1_i16).to_arg().as_sint(), Ok(-1));
282        assert_eq!((-1_i32).to_arg().as_sint(), Ok(-1));
283        assert_eq!((-1_i64).to_arg().as_sint(), Ok(-1));
284
285        assert_eq!((u64::MAX).to_arg().as_sint(), Err(Error::Overflow));
286    }
287}