use crate::ast::PrimitiveType;
use serde::{Deserialize, Serialize};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum NumberSourceKind {
Integer,
Float,
}
impl NumberSourceKind {
#[must_use]
pub const fn default_primitive(self) -> PrimitiveType {
match self {
Self::Integer => PrimitiveType::I32,
Self::Float => PrimitiveType::F64,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum NumericSuffix {
I32,
I64,
F32,
F64,
}
impl NumericSuffix {
#[must_use]
pub const fn primitive(self) -> PrimitiveType {
match self {
Self::I32 => PrimitiveType::I32,
Self::I64 => PrimitiveType::I64,
Self::F32 => PrimitiveType::F32,
Self::F64 => PrimitiveType::F64,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum NumberValue {
Integer(i128),
Float(f64),
}
impl NumberValue {
#[must_use]
pub fn as_i32(&self) -> Option<i32> {
match *self {
Self::Integer(v) => i32::try_from(v).ok(),
Self::Float(_) => None,
}
}
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
match *self {
Self::Integer(v) => i64::try_from(v).ok(),
Self::Float(_) => None,
}
}
#[must_use]
#[expect(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
reason = "best-effort backend cast; range-check happened at semantic time"
)]
pub const fn as_f32(&self) -> f32 {
match *self {
Self::Integer(v) => v as f32,
Self::Float(f) => f as f32,
}
}
#[must_use]
#[expect(
clippy::cast_precision_loss,
reason = "i128 → f64 may lose precision above 2^53; semantic gates this when it matters"
)]
pub const fn as_f64(&self) -> f64 {
match *self {
Self::Integer(v) => v as f64,
Self::Float(f) => f,
}
}
}
impl From<f64> for NumberValue {
#[expect(
clippy::cast_possible_truncation,
reason = "finite whole-number f64 fits i128 modulo magnitudes beyond 2^127, which the From<f64> heuristic does not promise to preserve exactly"
)]
fn from(value: f64) -> Self {
if value.is_finite() && value.fract() == 0.0 {
Self::Integer(value as i128)
} else {
Self::Float(value)
}
}
}
impl From<i128> for NumberValue {
fn from(value: i128) -> Self {
Self::Integer(value)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct NumberLiteral {
pub value: NumberValue,
pub suffix: Option<NumericSuffix>,
pub kind: NumberSourceKind,
}
impl NumberLiteral {
#[must_use]
pub const fn unsuffixed(value: i128) -> Self {
Self {
value: NumberValue::Integer(value),
suffix: None,
kind: NumberSourceKind::Integer,
}
}
#[must_use]
pub const fn unsuffixed_float(value: f64) -> Self {
Self {
value: NumberValue::Float(value),
suffix: None,
kind: NumberSourceKind::Float,
}
}
#[must_use]
pub const fn suffixed(value: NumberValue, suffix: NumericSuffix) -> Self {
let kind = match suffix {
NumericSuffix::I32 | NumericSuffix::I64 => NumberSourceKind::Integer,
NumericSuffix::F32 | NumericSuffix::F64 => NumberSourceKind::Float,
};
Self {
value,
suffix: Some(suffix),
kind,
}
}
#[must_use]
pub const fn from_lex(
value: NumberValue,
suffix: Option<NumericSuffix>,
kind: NumberSourceKind,
) -> Self {
Self {
value,
suffix,
kind,
}
}
#[must_use]
pub fn primitive_type(&self) -> PrimitiveType {
self.suffix
.map_or_else(|| self.kind.default_primitive(), NumericSuffix::primitive)
}
}
impl From<f64> for NumberLiteral {
fn from(value: f64) -> Self {
let kind = if value.is_finite() && value.fract() == 0.0 {
NumberSourceKind::Integer
} else {
NumberSourceKind::Float
};
Self {
value: NumberValue::from(value),
suffix: None,
kind,
}
}
}
impl From<i128> for NumberLiteral {
fn from(value: i128) -> Self {
Self {
value: NumberValue::Integer(value),
suffix: None,
kind: NumberSourceKind::Integer,
}
}
}