luau-syntax 0.732.0

Luau lexer, parser, AST, CST, and source utilities
Documentation
use super::*;
use crate::ast::ConstantNumberParseResult;

impl<'source, 'ast, 'name, 'names> Parser<'source, 'ast, 'name, 'names>
where
    'name: 'ast,
{
    pub(in crate::parser) fn parse_number_expression(
        &mut self,
        source: &'source [u8],
    ) -> Result<Expression<'ast>> {
        let location = self.current_token_location();
        let source = ascii_str(source);
        let cleaned_storage = clean_number(source);
        let cleaned = cleaned_storage.as_deref().unwrap_or(source);
        let cst_value = self.cst.enabled().then(|| self.current_source_string());

        if flags::LuauIntegerType2.get() && cleaned.ends_with('i') {
            let (value, parse_result) = parse_integer64(cleaned);
            self.advance();
            if parse_result == ConstantNumberParseResult::Malformed {
                let message_index =
                    self.report_parse_error(self.error_at(location, "Malformed integer"));
                return Ok(self.alloc_expression(ExpressionInit::new(
                    location,
                    ExpressionKind::Error {
                        expressions: self.empty_expression_slice(),
                        message_index,
                    },
                )));
            }
            if parse_result != ConstantNumberParseResult::Ok {
                let message_index =
                    self.report_parse_error(self.error_at(location, "Integer overflow"));
                return Ok(self.alloc_expression(ExpressionInit::new(
                    location,
                    ExpressionKind::Error {
                        expressions: self.empty_expression_slice(),
                        message_index,
                    },
                )));
            }
            let expression =
                self.arena
                    .alloc_expression_integer_direct(location, value, parse_result);
            let cst = cst_value
                .map(|value| CstNode::ExprConstantInteger(CstExprConstantInteger { value }));
            Ok(self.attach_expression_cst(expression, cst))
        } else {
            let (value, parse_result) = parse_number(cleaned);
            self.advance();
            if parse_result == ConstantNumberParseResult::Malformed {
                let message_index =
                    self.report_parse_error(self.error_at(location, "Malformed number"));
                return Ok(self.alloc_expression(ExpressionInit::new(
                    location,
                    ExpressionKind::Error {
                        expressions: self.empty_expression_slice(),
                        message_index,
                    },
                )));
            }
            let expression =
                self.arena
                    .alloc_expression_number_direct(location, value, parse_result);
            let cst =
                cst_value.map(|value| CstNode::ExprConstantNumber(CstExprConstantNumber { value }));
            Ok(self.attach_expression_cst(expression, cst))
        }
    }
}

fn parse_integer64(value: &str) -> (i64, ConstantNumberParseResult) {
    if let Some(hex) = value
        .strip_prefix("0x")
        .or_else(|| value.strip_prefix("0X"))
    {
        return integer64_from_radix(hex, 16);
    }
    if let Some(binary) = value
        .strip_prefix("0b")
        .or_else(|| value.strip_prefix("0B"))
    {
        return integer64_from_radix(binary, 2);
    }

    let Some(decimal) = value.strip_suffix('i') else {
        return (0, ConstantNumberParseResult::Malformed);
    };
    match decimal.parse() {
        Ok(value) => (value, ConstantNumberParseResult::Ok),
        Err(error) if is_integer_overflow(error.kind()) => (
            overflowed_i64(decimal),
            ConstantNumberParseResult::IntOverflow,
        ),
        Err(_) => (0, ConstantNumberParseResult::Malformed),
    }
}

fn integer64_from_radix(value: &str, radix: u32) -> (i64, ConstantNumberParseResult) {
    let Some(value) = value.strip_suffix('i') else {
        return (0, ConstantNumberParseResult::Malformed);
    };
    if value.is_empty()
        || !value
            .bytes()
            .all(|byte| digit_value(byte).is_some_and(|digit| digit < radix))
    {
        return (0, ConstantNumberParseResult::Malformed);
    }

    match u64::from_str_radix(value, radix) {
        Ok(value) => (value as i64, ConstantNumberParseResult::Ok),
        Err(error) if error.kind() == &std::num::IntErrorKind::PosOverflow => (
            u64::MAX as i64,
            if radix == 2 {
                ConstantNumberParseResult::BinOverflow
            } else {
                ConstantNumberParseResult::HexOverflow
            },
        ),
        Err(_) => (0, ConstantNumberParseResult::Malformed),
    }
}

fn parse_number(value: &str) -> (f64, ConstantNumberParseResult) {
    if let Some(hex) = value
        .strip_prefix("0x")
        .or_else(|| value.strip_prefix("0X"))
    {
        return number_from_radix(hex, 16);
    }
    if let Some(binary) = value
        .strip_prefix("0b")
        .or_else(|| value.strip_prefix("0B"))
    {
        return number_from_radix(binary, 2);
    }

    let Ok(parsed) = value.parse::<f64>() else {
        return (0.0, ConstantNumberParseResult::Malformed);
    };

    if parsed >= ((1_u64 << 53) as f64)
        && value.bytes().all(|byte| byte.is_ascii_digit())
        && format!("{parsed:.0}") != value
    {
        return (parsed, ConstantNumberParseResult::Imprecise);
    }

    (parsed, ConstantNumberParseResult::Ok)
}

fn number_from_radix(value: &str, radix: u32) -> (f64, ConstantNumberParseResult) {
    if value.is_empty()
        || !value
            .bytes()
            .all(|byte| digit_value(byte).is_some_and(|digit| digit < radix))
    {
        return (0.0, ConstantNumberParseResult::Malformed);
    }

    match u64::from_str_radix(value, radix) {
        Ok(value) => {
            let parsed = value as f64;
            if value >= (1_u64 << 53) && parsed as u64 != value {
                (parsed, ConstantNumberParseResult::Imprecise)
            } else {
                (parsed, ConstantNumberParseResult::Ok)
            }
        }
        Err(error) if error.kind() == &std::num::IntErrorKind::PosOverflow => (
            u64::MAX as f64,
            if radix == 2 {
                ConstantNumberParseResult::BinOverflow
            } else {
                ConstantNumberParseResult::HexOverflow
            },
        ),
        Err(_) => (0.0, ConstantNumberParseResult::Malformed),
    }
}

fn is_integer_overflow(kind: &std::num::IntErrorKind) -> bool {
    matches!(
        kind,
        std::num::IntErrorKind::PosOverflow | std::num::IntErrorKind::NegOverflow
    )
}

fn overflowed_i64(value: &str) -> i64 {
    if value.starts_with('-') {
        i64::MIN
    } else {
        i64::MAX
    }
}

fn digit_value(byte: u8) -> Option<u32> {
    match byte {
        b'0'..=b'9' => Some(u32::from(byte - b'0')),
        b'a'..=b'z' => Some(u32::from(byte - b'a' + 10)),
        b'A'..=b'Z' => Some(u32::from(byte - b'A' + 10)),
        _ => None,
    }
}

fn clean_number(value: &str) -> Option<String> {
    if value.as_bytes().contains(&b'_') {
        Some(value.chars().filter(|ch| *ch != '_').collect())
    } else {
        None
    }
}

fn ascii_str(value: &[u8]) -> &str {
    // Lexer::readNumber only admits ASCII digits, separators, and suffix bytes.
    unsafe { std::str::from_utf8_unchecked(value) }
}