litcheck_filecheck/expr/
num.rs

1use crate::common::*;
2
3#[derive(Debug, Diagnostic, thiserror::Error)]
4pub enum ParseNumberError {
5    #[error("expected unsigned number but found sign")]
6    #[diagnostic()]
7    UnexpectedSign {
8        #[label("occurs here")]
9        span: SourceSpan,
10    },
11    #[error("expected hexadecimal number with 0x prefix, but no prefix was present")]
12    #[diagnostic()]
13    MissingPrefix {
14        #[label("occurs here")]
15        span: SourceSpan,
16    },
17    #[error("input string has incorrect precision: expected {precision} got {actual}")]
18    #[diagnostic()]
19    PrecisionMismatch {
20        #[label("occurs here")]
21        span: SourceSpan,
22        precision: u8,
23        actual: usize,
24    },
25    #[error("input string has incorrect numeric format: {reason:?}")]
26    #[diagnostic()]
27    InvalidFormat {
28        #[label("occurs here")]
29        span: SourceSpan,
30        reason: core::num::IntErrorKind,
31    },
32}
33
34#[derive(Debug, Clone)]
35pub struct Number {
36    pub span: SourceSpan,
37    pub format: NumberFormat,
38    pub value: i64,
39}
40impl Number {
41    pub fn new(span: SourceSpan, value: i64) -> Self {
42        Self {
43            span,
44            format: NumberFormat::default(),
45            value,
46        }
47    }
48
49    pub fn new_with_format(span: SourceSpan, value: i64, format: NumberFormat) -> Self {
50        Self {
51            span,
52            format,
53            value,
54        }
55    }
56
57    pub fn parse_with_format(
58        input: Span<&str>,
59        format: NumberFormat,
60    ) -> Result<Self, ParseNumberError> {
61        let (span, input) = input.into_parts();
62
63        match format {
64            NumberFormat::Unsigned { precision } => {
65                if input.starts_with(['-', '+']) {
66                    return Err(ParseNumberError::UnexpectedSign { span });
67                }
68                let value = input
69                    .parse::<i64>()
70                    .map(|value| Self {
71                        span,
72                        format,
73                        value,
74                    })
75                    .map_err(|error| ParseNumberError::InvalidFormat {
76                        span,
77                        reason: error.kind().clone(),
78                    })?;
79                if precision == 0 {
80                    return Ok(value);
81                }
82                if input.len() != precision as usize {
83                    Err(ParseNumberError::PrecisionMismatch {
84                        span,
85                        precision,
86                        actual: input.len(),
87                    })
88                } else {
89                    Ok(value)
90                }
91            }
92            NumberFormat::Signed { precision } => {
93                let value = input
94                    .parse::<i64>()
95                    .map(|value| Self {
96                        span,
97                        format,
98                        value,
99                    })
100                    .map_err(|error| ParseNumberError::InvalidFormat {
101                        span,
102                        reason: error.kind().clone(),
103                    })?;
104                if precision == 0 {
105                    return Ok(value);
106                }
107                let actual = if let Some(input) = input.strip_prefix(['-', '+']) {
108                    input.len()
109                } else {
110                    input.len()
111                };
112                if actual != precision as usize {
113                    Err(ParseNumberError::PrecisionMismatch {
114                        span,
115                        precision,
116                        actual,
117                    })
118                } else {
119                    Ok(value)
120                }
121            }
122            NumberFormat::Hex {
123                require_prefix,
124                precision,
125            } => {
126                let input = match input.strip_prefix("0x") {
127                    None if require_prefix => return Err(ParseNumberError::MissingPrefix { span }),
128                    None => input,
129                    Some(input) => input,
130                };
131                let value = i64::from_str_radix(input, 16)
132                    .map(|value| Self {
133                        span,
134                        format,
135                        value,
136                    })
137                    .map_err(|error| ParseNumberError::InvalidFormat {
138                        span,
139                        reason: error.kind().clone(),
140                    })?;
141                if input.len() != precision as usize {
142                    Err(ParseNumberError::PrecisionMismatch {
143                        span,
144                        precision,
145                        actual: input.len(),
146                    })
147                } else {
148                    Ok(value)
149                }
150            }
151        }
152    }
153}
154impl Eq for Number {}
155impl PartialEq for Number {
156    fn eq(&self, other: &Self) -> bool {
157        self.value == other.value && self.format == other.format
158    }
159}
160impl PartialOrd for Number {
161    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
162        Some(self.value.cmp(&other.value))
163    }
164}
165impl Ord for Number {
166    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
167        self.value.cmp(&other.value)
168    }
169}
170impl Spanned for Number {
171    fn span(&self) -> SourceSpan {
172        self.span
173    }
174}
175impl fmt::Display for Number {
176    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177        let value = self.value;
178        match self.format {
179            NumberFormat::Unsigned { precision: 0 } => write!(f, "{}", value as u64),
180            NumberFormat::Unsigned { precision: n } => {
181                write!(f, "{:0n$}", value as u64, n = n as usize)
182            }
183            NumberFormat::Signed { precision: 0 } => write!(f, "{value}"),
184            NumberFormat::Signed { precision: n } => write!(f, "{value:0n$}", n = n as usize),
185            NumberFormat::Hex {
186                require_prefix: true,
187                precision: 0,
188            } => write!(f, "{value:#x?}"),
189            NumberFormat::Hex {
190                require_prefix: false,
191                precision: 0,
192            } => write!(f, "{value:x?}"),
193            NumberFormat::Hex {
194                require_prefix: true,
195                precision: n,
196            } => write!(f, "{value:#0n$x?}", n = n as usize),
197            NumberFormat::Hex {
198                require_prefix: false,
199                precision: n,
200            } => write!(f, "{value:0n$x?}", n = n as usize),
201        }
202    }
203}
204
205#[derive(Debug, Copy, Clone, PartialEq, Eq)]
206#[repr(u8)]
207pub enum NumberFormat {
208    Unsigned { precision: u8 },
209    Signed { precision: u8 },
210    Hex { precision: u8, require_prefix: bool },
211}
212impl NumberFormat {
213    pub fn describe(&self) -> Cow<'static, str> {
214        match self {
215            Self::Unsigned { precision: 0 } => Cow::Borrowed("any unsigned 64-bit integer"),
216            Self::Unsigned { precision } => {
217                Cow::Owned(format!("an unsigned {precision}-digit 64-bit integer"))
218            }
219            Self::Signed { precision: 0 } => Cow::Borrowed("any signed 64-bit integer"),
220            Self::Signed { precision } => {
221                Cow::Owned(format!("a signed {precision}-digit 64-bit integer"))
222            }
223            Self::Hex {
224                require_prefix: true,
225                precision: 0,
226            } => Cow::Borrowed("any 64-bit integer in hex format, prefixed with 0x"),
227            Self::Hex {
228                require_prefix: false,
229                precision: 0,
230            } => Cow::Borrowed("any 64-bit integer in hex format"),
231            Self::Hex {
232                require_prefix: true,
233                precision,
234            } => Cow::Owned(format!(
235                "a {precision}-digit 64-bit integer in hex format, prefixed with 0x"
236            )),
237            Self::Hex { precision, .. } => {
238                Cow::Owned(format!("a {precision}-digit 64-bit integer in hex format"))
239            }
240        }
241    }
242
243    pub fn is_signed(&self) -> bool {
244        matches!(self, Self::Signed { .. })
245    }
246
247    pub fn is_hex(&self) -> bool {
248        matches!(self, Self::Hex { .. })
249    }
250
251    pub fn precision(&self) -> usize {
252        match self {
253            Self::Unsigned { precision }
254            | Self::Signed { precision }
255            | Self::Hex { precision, .. } => *precision as usize,
256        }
257    }
258
259    pub fn discriminant(&self) -> u8 {
260        // SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)` `union`
261        // between `repr(C)` structs, each of which has the `u8` discriminant as its first
262        // field, so we can read the discriminant without offsetting the pointer.
263        unsafe { *<*const _>::from(self).cast::<u8>() }
264    }
265
266    pub fn pattern_nocapture(&self) -> Cow<'static, str> {
267        // NOTE: The patterns below with precision 0 have their
268        // range capped at 19, which is the maximum number of digits
269        // in i64::MAX, or the largest possible number that could
270        // be represented in decimal form
271        match self {
272            NumberFormat::Signed { precision: 0 } => Cow::Borrowed(r"(?:[-+]?[0-9]{1,19})"),
273            NumberFormat::Signed { precision } => {
274                Cow::Owned(format!("(?:[-+]?[0-9]{{{precision}}})"))
275            }
276            NumberFormat::Unsigned { precision: 0 } => Cow::Borrowed(r"(?:[0-9]{1,19})"),
277            NumberFormat::Unsigned { precision } => Cow::Owned(format!("(?:[0-9]{{{precision}}})")),
278            // The hex value for i64::MAX is 7fffffffffffffff,
279            // or 16 digits
280            NumberFormat::Hex {
281                require_prefix: true,
282                precision: 0,
283            } => Cow::Borrowed(r"(?:0x[A-Fa-f0-9]{1,16})"),
284            NumberFormat::Hex {
285                require_prefix: true,
286                precision,
287            } => Cow::Owned(format!("(?:0x[A-Fa-f0-9]{{{precision}}})")),
288            NumberFormat::Hex {
289                require_prefix: false,
290                precision: 0,
291            } => Cow::Borrowed(r"(?:[A-Fa-f0-9]{1,16})"),
292            NumberFormat::Hex {
293                require_prefix: false,
294                precision,
295            } => Cow::Owned(format!("(?:[A-Fa-f0-9]{{{precision}}})")),
296        }
297    }
298
299    pub fn pattern(&self, group_name_override: Option<&str>) -> Cow<'static, str> {
300        // NOTE: The patterns below with precision 0 have their
301        // range capped at 19, which is the maximum number of digits
302        // in i64::MAX, or the largest possible number that could
303        // be represented in decimal form
304        let group_name = group_name_override.unwrap_or("digits");
305        match self {
306            NumberFormat::Signed { precision: 0 } => match group_name_override {
307                None => Cow::Borrowed(r"(?P<digits>[-+]?[0-9]{1,19})"),
308                Some(group_name) => Cow::Owned(format!("(?P<{group_name}>[-+]?[0-9]{{1,19}})")),
309            },
310            NumberFormat::Signed { precision } => {
311                Cow::Owned(format!("(?P<{group_name}>[-+]?[0-9]{{{precision}}})"))
312            }
313            NumberFormat::Unsigned { precision: 0 } => match group_name_override {
314                None => Cow::Borrowed(r"(?P<digits>[0-9]{1,19})"),
315                Some(group_name) => Cow::Owned(format!("(?P<{group_name}>[0-9]{{1,19}})")),
316            },
317            NumberFormat::Unsigned { precision } => {
318                Cow::Owned(format!("(?P<{group_name}>[0-9]{{{precision}}})"))
319            }
320            // The hex value for i64::MAX is 7fffffffffffffff,
321            // or 16 digits
322            NumberFormat::Hex {
323                require_prefix: true,
324                precision: 0,
325            } => match group_name_override {
326                None => Cow::Borrowed(r"(?P<digits>0x[A-Fa-f0-9]{1,16})"),
327                Some(group_name) => Cow::Owned(format!("(?P<{group_name}>0x[A-Fa-f0-9]{{1,16}})")),
328            },
329            NumberFormat::Hex {
330                require_prefix: true,
331                precision,
332            } => Cow::Owned(format!("(?P<{group_name}>0x[A-Fa-f0-9]{{{precision}}})")),
333            NumberFormat::Hex {
334                require_prefix: false,
335                precision: 0,
336            } => match group_name_override {
337                None => Cow::Borrowed(r"(?P<digits>[A-Fa-f0-9]{1,16})"),
338                Some(group_name) => Cow::Owned(format!("(?P<{group_name}>[A-Fa-f0-9]{{1,16}})")),
339            },
340            NumberFormat::Hex {
341                require_prefix: false,
342                precision,
343            } => Cow::Owned(format!("(?P<{group_name}>[A-Fa-f0-9]{{{precision}}})")),
344        }
345    }
346}
347impl Default for NumberFormat {
348    fn default() -> Self {
349        Self::Unsigned { precision: 0 }
350    }
351}
352
353#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
354pub enum FormatSpecifier {
355    #[default]
356    Unsigned,
357    Signed,
358    Hex,
359}