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 {
unsafe { std::str::from_utf8_unchecked(value) }
}