litcheck_filecheck/expr/
num.rs

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