Skip to main content

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    #[error("input string has incorrect numeric format: invalid casing style, expected {expected}")]
35    #[diagnostic()]
36    InvalidCasing {
37        #[label("occurs here")]
38        span: SourceSpan,
39        expected: CasingStyle,
40    },
41}
42
43#[derive(Debug, Clone)]
44pub struct Number {
45    pub span: SourceSpan,
46    pub format: Option<NumberFormat>,
47    pub value: i128,
48}
49impl Number {
50    pub fn new(span: SourceSpan, value: i128) -> Self {
51        Self {
52            span,
53            format: None,
54            value,
55        }
56    }
57
58    pub fn new_with_format(span: SourceSpan, value: i128, format: NumberFormat) -> Self {
59        Self {
60            span,
61            format: Some(format),
62            value,
63        }
64    }
65
66    /// Returns the format that should be applied to an expression involving `self` and `other`.
67    ///
68    /// Returns `None` if there is an implicit format conflict. Otherwise this returns the format
69    /// to apply to the expression result.
70    ///
71    /// Two numbers are format-compatible if they:
72    ///
73    /// * Have the same format specifier (i.e. hex, signed, unsigned), or one of the operands
74    ///   has no specified format (i.e. it is inferred from the other operand).
75    /// * Have the same precision, if specified, or one of the operands is arbitrary precision
76    /// * If hex, both require a prefix, or neither do
77    /// * If hex, the casing style is compatible, i.e. they don't have conflicting casing
78    ///   requirements
79    ///
80    /// The format returned from this function will take the most precise information from the
81    /// given input formats, e.g. if the two numbers are both unsigned integers, but one has a
82    /// precision of 6, and the other is arbitrary precision, then the output format will specify
83    /// a precision of 6.
84    pub fn infer_expression_format(&self, other: &Self) -> Option<NumberFormat> {
85        match (self.format, other.format) {
86            (format @ Some(_), None) | (None, format @ Some(_)) => format,
87            (None, None) => Some(NumberFormat::default()),
88            (
89                Some(NumberFormat::Hex {
90                    require_prefix: lprefixed,
91                    casing: lcasing,
92                    precision: lprecision,
93                }),
94                Some(NumberFormat::Hex {
95                    require_prefix: rprefixed,
96                    casing: rcasing,
97                    precision: rprecision,
98                }),
99            ) if lprefixed == rprefixed
100                && (lprecision == rprecision || lprecision == 0 || rprecision == 0)
101                && (lcasing == rcasing
102                    || lcasing == CasingStyle::Any
103                    || rcasing == CasingStyle::Any) =>
104            {
105                let precision = core::cmp::max(lprecision, rprecision);
106                Some(NumberFormat::Hex {
107                    precision,
108                    require_prefix: lprefixed,
109                    casing: lcasing,
110                })
111            }
112            (
113                Some(NumberFormat::Signed {
114                    precision: lprecision,
115                }),
116                Some(NumberFormat::Signed {
117                    precision: rprecision,
118                }),
119            ) if lprecision == rprecision || lprecision == 0 || rprecision == 0 => {
120                Some(NumberFormat::Signed {
121                    precision: core::cmp::max(lprecision, rprecision),
122                })
123            }
124            (
125                Some(NumberFormat::Unsigned {
126                    precision: lprecision,
127                }),
128                Some(NumberFormat::Unsigned {
129                    precision: rprecision,
130                }),
131            ) if lprecision == rprecision || lprecision == 0 || rprecision == 0 => {
132                Some(NumberFormat::Unsigned {
133                    precision: core::cmp::max(lprecision, rprecision),
134                })
135            }
136            _ => None,
137        }
138    }
139
140    pub fn parse_with_format(
141        input: Span<&str>,
142        format: NumberFormat,
143    ) -> Result<Self, ParseNumberError> {
144        let (span, input) = input.into_parts();
145
146        match format {
147            NumberFormat::Unsigned { precision } => {
148                if input.starts_with(['-', '+']) {
149                    return Err(ParseNumberError::UnexpectedSign { span });
150                }
151                let value = input
152                    .parse::<i128>()
153                    .map(|value| Self {
154                        span,
155                        format: Some(format),
156                        value,
157                    })
158                    .map_err(|error| ParseNumberError::InvalidFormat {
159                        span,
160                        reason: *error.kind(),
161                    })?;
162                if precision == 0 {
163                    return Ok(value);
164                }
165                if input.len() < precision as usize {
166                    Err(ParseNumberError::PrecisionMismatch {
167                        span,
168                        precision,
169                        actual: input.len(),
170                    })
171                } else {
172                    Ok(value)
173                }
174            }
175            NumberFormat::Signed { precision } => {
176                let value = input
177                    .parse::<i128>()
178                    .map(|value| Self {
179                        span,
180                        format: Some(format),
181                        value,
182                    })
183                    .map_err(|error| ParseNumberError::InvalidFormat {
184                        span,
185                        reason: *error.kind(),
186                    })?;
187                if precision == 0 {
188                    return Ok(value);
189                }
190                let actual = if let Some(input) = input.strip_prefix(['-', '+']) {
191                    input.len()
192                } else {
193                    input.len()
194                };
195                if actual < precision as usize {
196                    Err(ParseNumberError::PrecisionMismatch {
197                        span,
198                        precision,
199                        actual,
200                    })
201                } else {
202                    Ok(value)
203                }
204            }
205            NumberFormat::Hex {
206                require_prefix,
207                precision,
208                casing,
209            } => {
210                let input = match input.strip_prefix("0x") {
211                    None if require_prefix => return Err(ParseNumberError::MissingPrefix { span }),
212                    None => input,
213                    Some(input) => input,
214                };
215                let is_valid_casing = match casing {
216                    CasingStyle::Any => true,
217                    CasingStyle::Lower => input.chars().all(|c| matches!(c, 'a'..='f' | '0'..='9')),
218                    CasingStyle::Upper => input.chars().all(|c| matches!(c, 'A'..='F' | '0'..='9')),
219                };
220                let value = i128::from_str_radix(input, 16)
221                    .map(|value| Self {
222                        span,
223                        format: Some(format),
224                        value,
225                    })
226                    .map_err(|error| ParseNumberError::InvalidFormat {
227                        span,
228                        reason: *error.kind(),
229                    })?;
230                if !is_valid_casing {
231                    Err(ParseNumberError::InvalidCasing {
232                        span,
233                        expected: casing,
234                    })
235                } else if precision > 0 && input.len() < precision as usize {
236                    Err(ParseNumberError::PrecisionMismatch {
237                        span,
238                        precision,
239                        actual: input.len(),
240                    })
241                } else {
242                    Ok(value)
243                }
244            }
245        }
246    }
247}
248impl Eq for Number {}
249impl PartialEq for Number {
250    fn eq(&self, other: &Self) -> bool {
251        self.value == other.value && self.format == other.format
252    }
253}
254impl PartialOrd for Number {
255    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
256        Some(self.cmp(other))
257    }
258}
259impl Ord for Number {
260    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
261        self.value.cmp(&other.value)
262    }
263}
264impl Spanned for Number {
265    fn span(&self) -> SourceSpan {
266        self.span
267    }
268}
269impl fmt::Display for Number {
270    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
271        let value = self.value;
272        let mut padding = 0;
273        let format = self.format.unwrap_or_default();
274        match format {
275            NumberFormat::Unsigned { precision } => {
276                if precision > 0 {
277                    padding += precision as usize;
278                }
279            }
280            NumberFormat::Signed { precision } => {
281                padding += value.is_negative() as usize;
282                if precision > 0 {
283                    padding += precision as usize;
284                }
285            }
286            NumberFormat::Hex {
287                precision,
288                require_prefix,
289                ..
290            } => {
291                padding += (require_prefix as usize) * 2;
292                if precision > 0 {
293                    padding += precision as usize;
294                }
295            }
296        }
297        match format {
298            NumberFormat::Unsigned { precision: 0 } => write!(f, "{}", value as u64),
299            NumberFormat::Unsigned { .. } => {
300                write!(f, "{:0padding$}", value as u64)
301            }
302            NumberFormat::Signed { precision: 0 } => write!(f, "{value}"),
303            NumberFormat::Signed { .. } => write!(f, "{value:0padding$}"),
304            NumberFormat::Hex {
305                require_prefix: true,
306                precision: 0,
307                casing: CasingStyle::Any | CasingStyle::Lower,
308            } => write!(f, "{value:#x?}"),
309            NumberFormat::Hex {
310                require_prefix: true,
311                precision: 0,
312                casing: CasingStyle::Upper,
313            } => write!(f, "{value:#X?}"),
314            NumberFormat::Hex {
315                require_prefix: false,
316                precision: 0,
317                casing: CasingStyle::Any | CasingStyle::Lower,
318            } => write!(f, "{value:x?}"),
319            NumberFormat::Hex {
320                require_prefix: false,
321                precision: 0,
322                casing: CasingStyle::Upper,
323            } => write!(f, "{value:X?}"),
324            NumberFormat::Hex {
325                require_prefix: true,
326                casing: CasingStyle::Any | CasingStyle::Lower,
327                ..
328            } => write!(f, "{value:#0padding$x?}"),
329            NumberFormat::Hex {
330                require_prefix: true,
331                casing: CasingStyle::Upper,
332                ..
333            } => write!(f, "{value:#0padding$X?}"),
334            NumberFormat::Hex {
335                require_prefix: false,
336                casing: CasingStyle::Any | CasingStyle::Lower,
337                ..
338            } => write!(f, "{value:0padding$x?}"),
339            NumberFormat::Hex {
340                require_prefix: false,
341                casing: CasingStyle::Upper,
342                ..
343            } => write!(f, "{value:0padding$X?}"),
344        }
345    }
346}
347
348#[derive(Debug, Copy, Clone, PartialEq, Eq)]
349#[repr(u8)]
350pub enum NumberFormat {
351    Unsigned {
352        precision: u8,
353    },
354    Signed {
355        precision: u8,
356    },
357    Hex {
358        precision: u8,
359        require_prefix: bool,
360        casing: CasingStyle,
361    },
362}
363
364#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
365#[repr(u8)]
366pub enum CasingStyle {
367    #[default]
368    Any = 0,
369    Upper,
370    Lower,
371}
372
373impl CasingStyle {
374    const HEX_ALPHABETS: &[&str] = &["[A-Fa-f0-9]", "[A-F0-9]", "[a-f0-9]"];
375
376    pub const fn as_hex_class(self) -> &'static str {
377        Self::HEX_ALPHABETS[self as u8 as usize]
378    }
379}
380
381impl fmt::Display for CasingStyle {
382    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383        match self {
384            Self::Any => f.write_str("either upper or lowercase"),
385            Self::Upper => f.write_str("uppercase"),
386            Self::Lower => f.write_str("lowercase"),
387        }
388    }
389}
390
391impl NumberFormat {
392    pub fn describe(&self) -> Cow<'static, str> {
393        match self {
394            Self::Unsigned { precision: 0 } => Cow::Borrowed("any unsigned 128-bit integer"),
395            Self::Unsigned { precision } => {
396                Cow::Owned(format!("an unsigned {precision}-digit 128-bit integer"))
397            }
398            Self::Signed { precision: 0 } => Cow::Borrowed("any signed 128-bit integer"),
399            Self::Signed { precision } => {
400                Cow::Owned(format!("a signed {precision}-digit 128-bit integer"))
401            }
402            Self::Hex {
403                require_prefix: true,
404                precision: 0,
405                casing,
406            } => Cow::Owned(format!(
407                "any 128-bit integer in {casing} hex format, prefixed with 0x"
408            )),
409            Self::Hex {
410                require_prefix: false,
411                precision: 0,
412                casing,
413            } => Cow::Owned(format!("any 128-bit integer in {casing} hex format")),
414            Self::Hex {
415                require_prefix: true,
416                precision,
417                casing,
418            } => Cow::Owned(format!(
419                "a {precision}-digit 12864-bit integer in {casing} hex format, prefixed with 0x"
420            )),
421            Self::Hex {
422                precision, casing, ..
423            } => Cow::Owned(format!(
424                "a {precision}-digit 128-bit integer in {casing} hex format"
425            )),
426        }
427    }
428
429    pub fn is_signed(&self) -> bool {
430        matches!(self, Self::Signed { .. })
431    }
432
433    pub fn is_hex(&self) -> bool {
434        matches!(self, Self::Hex { .. })
435    }
436
437    pub fn precision(&self) -> usize {
438        match self {
439            Self::Unsigned { precision }
440            | Self::Signed { precision }
441            | Self::Hex { precision, .. } => *precision as usize,
442        }
443    }
444
445    pub fn discriminant(&self) -> u8 {
446        // SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)` `union`
447        // between `repr(C)` structs, each of which has the `u8` discriminant as its first
448        // field, so we can read the discriminant without offsetting the pointer.
449        unsafe { *<*const _>::from(self).cast::<u8>() }
450    }
451
452    pub fn pattern_nocapture(&self) -> Cow<'static, str> {
453        // NOTE: The patterns below with precision 0 have their range capped at 39, which is the
454        // maximum number of digits in i128::MAX, or the largest possible number that could be
455        // represented in decimal form
456        match self {
457            NumberFormat::Signed { precision: 0 } => Cow::Borrowed(r"(?:[-+]?[0-9]{1,39})"),
458            NumberFormat::Signed { precision } => {
459                Cow::Owned(format!("(?:[-+]?[0-9]{{{precision},39}})"))
460            }
461            NumberFormat::Unsigned { precision: 0 } => Cow::Borrowed(r"(?:[0-9]{1,39})"),
462            NumberFormat::Unsigned { precision } => {
463                Cow::Owned(format!("(?:[0-9]{{{precision},39}})"))
464            }
465            // The hex value for i128::MAX is 7fffffffffffffffffffffffffffffff or 32 digits
466            NumberFormat::Hex {
467                require_prefix: true,
468                precision: 0,
469                casing,
470            } => Cow::Owned(format!("(?:0x{}{{1,32}})", casing.as_hex_class())),
471            NumberFormat::Hex {
472                require_prefix: true,
473                precision,
474                casing,
475            } => Cow::Owned(format!("(?:0x{}{{{precision},32}})", casing.as_hex_class())),
476            NumberFormat::Hex {
477                require_prefix: false,
478                precision: 0,
479                casing,
480            } => Cow::Owned(format!(r"(?:{}{{1,32}})", casing.as_hex_class())),
481            NumberFormat::Hex {
482                require_prefix: false,
483                precision,
484                casing,
485            } => Cow::Owned(format!("(?:{}{{{precision},32}})", casing.as_hex_class())),
486        }
487    }
488
489    pub fn pattern(&self, group_name_override: Option<&str>) -> Cow<'static, str> {
490        // NOTE: The patterns below with precision 0 have their range capped at 39, which is the
491        // maximum number of digits in i128::MAX, or the largest possible number that could be
492        // represented in decimal form
493        let group_name = group_name_override.unwrap_or("digits");
494        match self {
495            NumberFormat::Signed { precision: 0 } => match group_name_override {
496                None => Cow::Borrowed(r"(?P<digits>[-+]?[0-9]{1,39})"),
497                Some(group_name) => Cow::Owned(format!("(?P<{group_name}>[-+]?[0-9]{{1,39}})")),
498            },
499            NumberFormat::Signed { precision } => {
500                Cow::Owned(format!("(?P<{group_name}>[-+]?[0-9]{{{precision},39}})"))
501            }
502            NumberFormat::Unsigned { precision: 0 } => match group_name_override {
503                None => Cow::Borrowed(r"(?P<digits>[0-9]{1,39})"),
504                Some(group_name) => Cow::Owned(format!("(?P<{group_name}>[0-9]{{1,39}})")),
505            },
506            NumberFormat::Unsigned { precision } => {
507                Cow::Owned(format!("(?P<{group_name}>[0-9]{{{precision},39}})"))
508            }
509            // The hex value for i128::MAX is 7fffffffffffffffffffffffffffffff or 32 digits
510            NumberFormat::Hex {
511                require_prefix: true,
512                precision: 0,
513                casing,
514            } => Cow::Owned(format!(
515                "(?P<{group_name}>0x{}{{1,32}})",
516                casing.as_hex_class()
517            )),
518            NumberFormat::Hex {
519                require_prefix: true,
520                precision,
521                casing,
522            } => Cow::Owned(format!(
523                "(?P<{group_name}>0x{}{{{precision},32}})",
524                casing.as_hex_class()
525            )),
526            NumberFormat::Hex {
527                require_prefix: false,
528                precision: 0,
529                casing,
530            } => Cow::Owned(format!(
531                "(?P<{group_name}>{}{{1,32}})",
532                casing.as_hex_class()
533            )),
534            NumberFormat::Hex {
535                require_prefix: false,
536                precision,
537                casing,
538            } => Cow::Owned(format!(
539                "(?P<{group_name}>{}{{{precision},32}})",
540                casing.as_hex_class()
541            )),
542        }
543    }
544}
545
546impl fmt::Display for NumberFormat {
547    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
548        match self {
549            Self::Unsigned { precision: 0 } => f.write_str("%u"),
550            Self::Unsigned { precision } => write!(f, "%.{precision}u"),
551            Self::Signed { precision: 0 } => f.write_str("d"),
552            Self::Signed { precision } => write!(f, "%.{precision}d"),
553            Self::Hex {
554                precision: 0,
555                require_prefix: false,
556                casing: CasingStyle::Any | CasingStyle::Lower,
557            } => write!(f, "%x"),
558            Self::Hex {
559                precision: 0,
560                require_prefix: false,
561                casing: CasingStyle::Upper,
562            } => write!(f, "%X"),
563            Self::Hex {
564                precision: 0,
565                require_prefix: true,
566                casing: CasingStyle::Any | CasingStyle::Lower,
567            } => write!(f, "%#x"),
568            Self::Hex {
569                precision: 0,
570                require_prefix: true,
571                casing: CasingStyle::Upper,
572            } => write!(f, "%#X"),
573            Self::Hex {
574                precision,
575                require_prefix: false,
576                casing: CasingStyle::Any | CasingStyle::Lower,
577            } => write!(f, "%.{precision}x"),
578            Self::Hex {
579                precision,
580                require_prefix: false,
581                casing: CasingStyle::Upper,
582            } => write!(f, "%.{precision}X"),
583            Self::Hex {
584                precision,
585                require_prefix: true,
586                casing: CasingStyle::Any | CasingStyle::Lower,
587            } => write!(f, "%#.{precision}x"),
588            Self::Hex {
589                precision,
590                require_prefix: true,
591                casing: CasingStyle::Upper,
592            } => write!(f, "%#.{precision}X"),
593        }
594    }
595}
596
597impl Default for NumberFormat {
598    fn default() -> Self {
599        Self::Unsigned { precision: 0 }
600    }
601}
602
603#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
604pub enum FormatSpecifier {
605    #[default]
606    Unsigned,
607    Signed,
608    Hex(CasingStyle),
609}