Skip to main content

nwnrs_nwscript/
int_literal.rs

1//! Integer literal parsing aligned with the native `NWScript` compiler.
2//!
3//! The upstream parser accumulates integer digits directly into an `int32_t`
4//! without range checks. That means oversized decimal, hex, binary, and octal
5//! literals wrap using two's-complement arithmetic instead of failing to parse.
6
7#[derive(#[automatically_derived]
impl ::core::fmt::Debug for IntegerLiteralError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f, "IntegerLiteralError")
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for IntegerLiteralError {
    #[inline]
    fn clone(&self) -> IntegerLiteralError { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for IntegerLiteralError { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for IntegerLiteralError {
    #[inline]
    fn eq(&self, other: &IntegerLiteralError) -> bool { true }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for IntegerLiteralError {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {}
}Eq)]
8pub(crate) struct IntegerLiteralError;
9
10pub(crate) fn parse_wrapping_decimal_i32(input: &str) -> Result<i32, IntegerLiteralError> {
11    let mut chars = input.chars();
12    let mut sign = 1i32;
13    let mut value = 0i32;
14
15    if #[allow(non_exhaustive_omitted_patterns)] match chars.clone().next() {
    Some('-') => true,
    _ => false,
}matches!(chars.clone().next(), Some('-')) {
16        sign = -1;
17        chars.next();
18    }
19
20    let mut saw_digit = false;
21    for ch in chars {
22        let Some(digit) = ch.to_digit(10) else {
23            return Err(IntegerLiteralError);
24        };
25        saw_digit = true;
26        value = value.wrapping_mul(10).wrapping_add(digit.cast_signed());
27    }
28
29    if !saw_digit {
30        return Err(IntegerLiteralError);
31    }
32
33    if sign == -1 {
34        value = value.wrapping_neg();
35    }
36
37    Ok(value)
38}
39
40pub(crate) fn parse_wrapping_prefixed_i32(
41    input: &str,
42    radix: u32,
43) -> Result<i32, IntegerLiteralError> {
44    let digits = input.get(2..).ok_or(IntegerLiteralError)?;
45    if digits.is_empty() {
46        return Err(IntegerLiteralError);
47    }
48
49    let mut value = 0i32;
50    for ch in digits.chars() {
51        let Some(digit) = ch.to_digit(radix) else {
52            return Err(IntegerLiteralError);
53        };
54        value = value
55            .wrapping_mul(radix.cast_signed())
56            .wrapping_add(digit.cast_signed());
57    }
58
59    Ok(value)
60}
61
62#[cfg(test)]
63mod tests {
64    use super::{parse_wrapping_decimal_i32, parse_wrapping_prefixed_i32};
65
66    #[test]
67    fn wraps_decimal_literals_like_upstream() {
68        assert_eq!(parse_wrapping_decimal_i32("2147483647"), Ok(i32::MAX));
69        assert_eq!(parse_wrapping_decimal_i32("2147483648"), Ok(i32::MIN));
70        assert_eq!(parse_wrapping_decimal_i32("-2147483648"), Ok(i32::MIN));
71    }
72
73    #[test]
74    fn wraps_prefixed_literals_like_upstream() {
75        assert_eq!(parse_wrapping_prefixed_i32("0xffffffff", 16), Ok(-1));
76        assert_eq!(parse_wrapping_prefixed_i32("0x80000000", 16), Ok(i32::MIN));
77        assert_eq!(
78            parse_wrapping_prefixed_i32("0b11111111111111111111111111111111", 2),
79            Ok(-1)
80        );
81        assert_eq!(
82            parse_wrapping_prefixed_i32("0o20000000000", 8),
83            Ok(i32::MIN)
84        );
85    }
86}