use rucc_base::float::{Float, Format, ParseError, Status};
use rucc_session::Std;
use rucc_target::{Arch, TargetInfo};
use rucc_types::{IntKind, int_width};
use crate::remarks::Remarks;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IntConstant {
pub value: u128,
pub ty: IntConstantType,
pub remarks: Remarks,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntConstantType {
Standard(IntKind),
BitInt {
signed: bool,
width: u32,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntError {
Floating,
InvalidSuffix,
InvalidOctalDigit,
NoDigits,
TooLarge,
}
impl IntError {
#[must_use]
pub const fn message(self) -> &'static str {
match self {
IntError::Floating => "not an integer constant",
IntError::InvalidSuffix => "invalid suffix on integer constant",
IntError::InvalidOctalDigit => "invalid digit in octal constant",
IntError::NoDigits => "no digits in integer constant",
IntError::TooLarge => "integer constant is too large to be represented in any type",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FloatConstant {
pub value: Float,
pub ty: FloatConstantType,
pub imaginary: bool,
pub remarks: Remarks,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FloatConstantType {
Float,
Double,
LongDouble,
Float16,
Float32,
Float64,
Float128,
Float32x,
Float64x,
Float80,
}
impl FloatConstantType {
#[must_use]
pub fn format(self, target: &TargetInfo) -> Format {
match self {
FloatConstantType::Float | FloatConstantType::Float32 => Format::Single,
FloatConstantType::Double
| FloatConstantType::Float64
| FloatConstantType::Float32x => Format::Double,
FloatConstantType::LongDouble => target.long_double_format,
FloatConstantType::Float16 => Format::Half,
FloatConstantType::Float128 => Format::Quad,
FloatConstantType::Float64x if target.triple.arch == Arch::X86_64 => {
Format::X87Extended
}
FloatConstantType::Float64x => Format::Quad,
FloatConstantType::Float80 => Format::X87Extended,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FloatError {
Integer,
InvalidSuffix,
MissingExponent,
NoExponentDigits,
NoDigits,
TooManyPoints,
DecimalFloat,
UnsupportedType,
}
impl FloatError {
#[must_use]
pub const fn message(self) -> &'static str {
match self {
FloatError::Integer => "not a floating constant",
FloatError::InvalidSuffix => "invalid suffix on floating constant",
FloatError::MissingExponent => "hexadecimal floating constants require an exponent",
FloatError::NoExponentDigits => "exponent has no digits",
FloatError::NoDigits => "no digits in floating constant",
FloatError::TooManyPoints => "too many decimal points in number",
FloatError::DecimalFloat => "decimal floating constants are not supported yet",
FloatError::UnsupportedType => {
"the type of this floating constant is not supported on this target"
}
}
}
}
pub fn integer(text: &str, std: Std, target: &TargetInfo) -> Result<IntConstant, IntError> {
let bytes = text.as_bytes();
let (base, start) = base_of(bytes);
if is_floating(bytes, base) {
return Err(IntError::Floating);
}
let mut remarks = Remarks::NONE;
if base == 2 && std < Std::C23 {
remarks = remarks.with(Remarks::BINARY);
}
let mut value: u128 = 0;
let mut digits = 0;
let mut index = start;
while index < bytes.len() {
let byte = bytes[index];
if byte == b'\'' {
if digits == 0 || index + 1 >= bytes.len() || digit(bytes[index + 1], base).is_none() {
return Err(IntError::InvalidSuffix);
}
if std < Std::C23 {
remarks = remarks.with(Remarks::SEPARATORS);
}
index += 1;
continue;
}
let Some(digit) = digit(byte, base) else {
break;
};
value = value
.checked_mul(u128::from(base))
.and_then(|shifted| shifted.checked_add(u128::from(digit)))
.ok_or(IntError::TooLarge)?;
digits += 1;
index += 1;
}
if digits == 0 {
return Err(IntError::NoDigits);
}
if base == 8 && bytes[start..index].iter().any(|&byte| byte == b'8' || byte == b'9') {
return Err(IntError::InvalidOctalDigit);
}
let suffix = suffix_of(&bytes[index..])?;
if suffix.length == Some(Length::LongLong) && std == Std::C89 {
remarks = remarks.with(Remarks::LONG_LONG);
}
if suffix.length == Some(Length::BitInt) {
if std < Std::C23 {
remarks = remarks.with(Remarks::BIT_INT);
}
return Ok(IntConstant { value, ty: bit_int(value, suffix.unsigned), remarks });
}
let candidates = candidates(base, suffix, std);
let kind = candidates
.iter()
.copied()
.find(|&kind| fits(value, kind, target))
.ok_or(IntError::TooLarge)?;
if base == 10 && !suffix.unsigned && !signed_standard(kind) {
remarks = remarks.with(Remarks::UNSIGNED);
}
Ok(IntConstant { value, ty: IntConstantType::Standard(kind), remarks })
}
fn base_of(bytes: &[u8]) -> (u32, usize) {
match bytes {
[b'0', b'x' | b'X', ..] => (16, 2),
[b'0', b'b' | b'B', ..] => (2, 2),
[b'0', next, ..] if next.is_ascii_digit() => (8, 1),
_ => (10, 0),
}
}
fn is_floating(bytes: &[u8], base: u32) -> bool {
let exponent = if base == 16 { *b"pP" } else { *b"eE" };
bytes.iter().any(|&byte| byte == b'.' || exponent.contains(&byte))
}
fn digit(byte: u8, base: u32) -> Option<u32> {
char::from(byte).to_digit(if base == 8 { 10 } else { base })
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Length {
Long,
LongLong,
BitInt,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Suffix {
unsigned: bool,
length: Option<Length>,
}
fn suffix_of(mut rest: &[u8]) -> Result<Suffix, IntError> {
let mut suffix = Suffix { unsigned: false, length: None };
while let Some(&byte) = rest.first() {
let taken = match byte {
b'u' | b'U' if !suffix.unsigned => {
suffix.unsigned = true;
1
}
b'l' | b'L' if suffix.length.is_none() => {
if rest.get(1) == Some(&byte) {
suffix.length = Some(Length::LongLong);
2
} else {
suffix.length = Some(Length::Long);
1
}
}
b'w' | b'W' if suffix.length.is_none() => {
let second = if byte == b'w' { b'b' } else { b'B' };
if rest.get(1) != Some(&second) {
return Err(IntError::InvalidSuffix);
}
suffix.length = Some(Length::BitInt);
2
}
_ => return Err(IntError::InvalidSuffix),
};
rest = &rest[taken..];
}
Ok(suffix)
}
fn bit_int(value: u128, unsigned: bool) -> IntConstantType {
let used = 128 - value.leading_zeros();
let width = if unsigned { used.max(1) } else { used + 1 };
IntConstantType::BitInt { signed: !unsigned, width: width.max(if unsigned { 1 } else { 2 }) }
}
fn signed_standard(kind: IntKind) -> bool {
matches!(kind, IntKind::Int | IntKind::Long | IntKind::LongLong)
}
fn fits(value: u128, kind: IntKind, target: &TargetInfo) -> bool {
let width = int_width(kind, target);
let bits = if kind.is_signed(false) { width - 1 } else { width };
bits >= 128 || value >> bits == 0
}
fn candidates(base: u32, suffix: Suffix, std: Std) -> &'static [IntKind] {
use IntKind::{Int, Int128, Long, LongLong, UInt, UInt128, ULong, ULongLong};
let decimal = base == 10;
let c89 = std == Std::C89;
match (suffix.unsigned, suffix.length) {
(false, None) if decimal && c89 => &[Int, Long, ULong, Int128, UInt128],
(false, None) if decimal => &[Int, Long, LongLong, Int128],
(false, None) if c89 => &[Int, UInt, Long, ULong, Int128, UInt128],
(false, None) => &[Int, UInt, Long, ULong, LongLong, ULongLong, Int128, UInt128],
(true, None) if c89 => &[UInt, ULong, UInt128],
(true, None) => &[UInt, ULong, ULongLong, UInt128],
(false, Some(Length::Long)) if decimal && c89 => &[Long, ULong, Int128, UInt128],
(false, Some(Length::Long)) if decimal => &[Long, LongLong, Int128],
(false, Some(Length::Long)) if c89 => &[Long, ULong, Int128, UInt128],
(false, Some(Length::Long)) => &[Long, ULong, LongLong, ULongLong, Int128, UInt128],
(true, Some(Length::Long)) if c89 => &[ULong, UInt128],
(true, Some(Length::Long)) => &[ULong, ULongLong, UInt128],
(false, Some(Length::LongLong)) if decimal => &[LongLong, Int128],
(false, Some(Length::LongLong)) => &[LongLong, ULongLong, Int128, UInt128],
(true, Some(Length::LongLong)) => &[ULongLong, UInt128],
(_, Some(Length::BitInt)) => &[],
}
}
pub fn floating(text: &str, std: Std, target: &TargetInfo) -> Result<FloatConstant, FloatError> {
let bytes = text.as_bytes();
let (base, _) = base_of(bytes);
if !is_floating(bytes, base) {
return Err(FloatError::Integer);
}
let hex = base == 16;
let base = if hex { 16 } else { 10 };
let mut remarks = Remarks::NONE;
if hex && std < Std::C99 {
remarks = remarks.with(Remarks::HEX_FLOAT);
}
let mut index = if hex { 2 } else { 0 };
let mut digits = 0;
let mut point = false;
let mut separators = false;
while index < bytes.len() {
let byte = bytes[index];
if byte == b'\'' {
if digits == 0 || !next_is_digit(bytes, index, base) {
return Err(FloatError::InvalidSuffix);
}
separators = true;
} else if byte == b'.' {
if point {
return Err(FloatError::TooManyPoints);
}
point = true;
} else if digit(byte, base).is_some() {
digits += 1;
} else {
break;
}
index += 1;
}
if digits == 0 {
return Err(FloatError::NoDigits);
}
let marker = if hex { *b"pP" } else { *b"eE" };
if index < bytes.len() && marker.contains(&bytes[index]) {
index += 1;
if matches!(bytes.get(index), Some(b'+' | b'-')) {
index += 1;
}
let mut exponent_digits = 0;
while index < bytes.len() {
let byte = bytes[index];
if byte == b'\'' {
if exponent_digits == 0 || !next_is_digit(bytes, index, 10) {
return Err(FloatError::InvalidSuffix);
}
separators = true;
} else if byte.is_ascii_digit() {
exponent_digits += 1;
} else {
break;
}
index += 1;
}
if exponent_digits == 0 {
return Err(FloatError::NoExponentDigits);
}
} else if hex {
return Err(FloatError::MissingExponent);
}
if separators && std < Std::C23 {
remarks = remarks.with(Remarks::SEPARATORS);
}
let suffix = float_suffix(&bytes[index..], target)?;
remarks = remarks.with(suffix.remarks);
let (value, status) =
Float::parse(&text[..index], suffix.ty.format(target)).map_err(|error| match error {
ParseError::NoDigits => FloatError::NoDigits,
ParseError::NoExponentDigits => FloatError::NoExponentDigits,
ParseError::Invalid => FloatError::InvalidSuffix,
})?;
if status.has(Status::OVERFLOW) {
remarks = remarks.with(Remarks::OUT_OF_RANGE);
}
if status.has(Status::UNDERFLOW) && value.is_zero() {
remarks = remarks.with(Remarks::TRUNCATED);
}
Ok(FloatConstant { value, ty: suffix.ty, imaginary: suffix.imaginary, remarks })
}
fn next_is_digit(bytes: &[u8], index: usize, base: u32) -> bool {
bytes.get(index + 1).is_some_and(|&next| digit(next, base).is_some())
}
struct FloatSuffix {
ty: FloatConstantType,
imaginary: bool,
remarks: Remarks,
}
fn float_suffix(mut rest: &[u8], target: &TargetInfo) -> Result<FloatSuffix, FloatError> {
let mut ty = None;
let mut imaginary = false;
let mut remarks = Remarks::NONE;
while let Some(&byte) = rest.first() {
let taken = match byte {
b'i' | b'j' | b'I' | b'J' if !imaginary => {
imaginary = true;
remarks = remarks.with(Remarks::IMAGINARY);
1
}
_ if ty.is_some() => return Err(FloatError::InvalidSuffix),
b'f' | b'F' => {
let (named, taken, extra) = float_n(rest)?;
ty = Some(named);
remarks = remarks.with(extra);
taken
}
b'l' | b'L' => {
ty = Some(FloatConstantType::LongDouble);
1
}
b'q' | b'Q' => {
ty = Some(FloatConstantType::Float128);
remarks = remarks.with(Remarks::EXTENDED_SUFFIX);
1
}
b'w' | b'W' => {
if target.triple.arch != Arch::X86_64 {
return Err(FloatError::UnsupportedType);
}
ty = Some(FloatConstantType::Float80);
remarks = remarks.with(Remarks::EXTENDED_SUFFIX);
1
}
b'd' | b'D' => {
let second = rest.get(1).copied();
let decimal = if byte == b'd' {
matches!(second, Some(b'f' | b'd' | b'l'))
} else {
matches!(second, Some(b'F' | b'D' | b'L'))
};
if decimal {
return Err(FloatError::DecimalFloat);
}
ty = Some(FloatConstantType::Double);
remarks = remarks.with(Remarks::DOUBLE_SUFFIX);
1
}
_ => return Err(FloatError::InvalidSuffix),
};
rest = &rest[taken..];
}
Ok(FloatSuffix { ty: ty.unwrap_or(FloatConstantType::Double), imaginary, remarks })
}
fn float_n(rest: &[u8]) -> Result<(FloatConstantType, usize, Remarks), FloatError> {
let mut end = 1;
while rest.get(end).is_some_and(u8::is_ascii_digit) {
end += 1;
}
if end == 1 {
return Ok((FloatConstantType::Float, 1, Remarks::NONE));
}
let extended = rest.get(end) == Some(&b'x');
let ty = match (&rest[1..end], extended) {
(b"16", false) => FloatConstantType::Float16,
(b"32", false) => FloatConstantType::Float32,
(b"64", false) => FloatConstantType::Float64,
(b"128", false) => FloatConstantType::Float128,
(b"32", true) => FloatConstantType::Float32x,
(b"64", true) => FloatConstantType::Float64x,
(b"128", true) => return Err(FloatError::UnsupportedType),
_ => return Err(FloatError::InvalidSuffix),
};
Ok((ty, end + usize::from(extended), Remarks::EXTENDED_SUFFIX))
}
#[cfg(test)]
mod tests {
use rucc_target::Triple;
use super::*;
fn linux() -> TargetInfo {
TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().expect("a known triple"))
}
fn aarch64() -> TargetInfo {
TargetInfo::new("aarch64-unknown-linux-gnu".parse::<Triple>().expect("a known triple"))
}
fn c23(text: &str) -> Result<IntConstant, IntError> {
integer(text, Std::C23, &linux())
}
fn kind(text: &str, std: Std) -> IntKind {
match integer(text, std, &linux()).expect("a valid constant").ty {
IntConstantType::Standard(kind) => kind,
IntConstantType::BitInt { .. } => panic!("{text} is a _BitInt constant"),
}
}
#[test]
fn a_constant_in_each_base_has_the_value_it_says() {
assert_eq!(c23("0").expect("zero").value, 0);
assert_eq!(c23("42").expect("decimal").value, 42);
assert_eq!(c23("0777").expect("octal").value, 0o777);
assert_eq!(c23("0xdeadBEEF").expect("hex").value, 0xdead_beef);
assert_eq!(c23("0b1010").expect("binary").value, 0b1010);
assert_eq!(c23("0X10").expect("upper case prefix").value, 16);
assert_eq!(c23("0u").expect("zero with a suffix").value, 0);
}
#[test]
fn digit_separators_are_stripped_and_reported_before_c23() {
let value = c23("1'000'000").expect("a C23 constant");
assert_eq!(value.value, 1_000_000);
assert!(value.remarks.is_none());
assert_eq!(c23("0x1'0").expect("hex with a separator").value, 16);
let older = integer("1'000", Std::C17, &linux()).expect("still converted");
assert!(older.remarks.has(Remarks::SEPARATORS));
assert_eq!(older.value, 1000);
}
#[test]
fn the_type_of_a_decimal_constant_walks_the_signed_types_only() {
assert_eq!(kind("2147483647", Std::C23), IntKind::Int);
assert_eq!(kind("2147483648", Std::C23), IntKind::Long);
assert_eq!(kind("4294967295", Std::C23), IntKind::Long);
assert_eq!(kind("9223372036854775807", Std::C23), IntKind::Long);
assert_eq!(kind("9223372036854775808", Std::C23), IntKind::Int128);
assert_eq!(kind("18446744073709551615", Std::C23), IntKind::Int128);
let large = c23("18446744073709551615").expect("fits __int128");
assert!(large.remarks.has(Remarks::UNSIGNED));
}
#[test]
fn a_constant_in_another_base_may_be_unsigned_without_saying_so() {
assert_eq!(kind("0xffffffff", Std::C23), IntKind::UInt);
assert_eq!(kind("0x7fffffff", Std::C23), IntKind::Int);
assert_eq!(kind("0x80000000", Std::C23), IntKind::UInt);
assert_eq!(kind("0x100000000", Std::C23), IntKind::Long);
assert_eq!(kind("0xffffffffffffffff", Std::C23), IntKind::ULong);
assert_eq!(kind("0777", Std::C23), IntKind::Int);
assert_eq!(kind("0b1010", Std::C23), IntKind::Int);
assert!(c23("0xffffffff").expect("a constant").remarks.is_none());
}
#[test]
fn c89_has_unsigned_long_in_the_decimal_list_and_no_long_long_in_any() {
assert_eq!(kind("18446744073709551615", Std::C89), IntKind::ULong);
assert_eq!(kind("18446744073709551615", Std::C99), IntKind::Int128);
let old = integer("18446744073709551615", Std::C89, &linux()).expect("a C89 constant");
assert!(old.remarks.has(Remarks::UNSIGNED));
let long_long = integer("1ll", Std::C89, &linux()).expect("an extension");
assert!(long_long.remarks.has(Remarks::LONG_LONG));
assert_eq!(kind("1ll", Std::C89), IntKind::LongLong);
assert!(integer("1ll", Std::C99, &linux()).expect("standard").remarks.is_none());
}
#[test]
fn a_suffix_narrows_the_list_it_does_not_pick_the_type() {
assert_eq!(kind("1u", Std::C23), IntKind::UInt);
assert_eq!(kind("1l", Std::C23), IntKind::Long);
assert_eq!(kind("1ul", Std::C23), IntKind::ULong);
assert_eq!(kind("1ll", Std::C23), IntKind::LongLong);
assert_eq!(kind("1llu", Std::C23), IntKind::ULongLong);
assert_eq!(kind("4294967296u", Std::C23), IntKind::ULong);
assert_eq!(kind("0xffffffffu", Std::C23), IntKind::UInt);
}
#[test]
fn the_letters_of_a_suffix_may_be_in_either_case_but_not_both() {
for text in ["1u", "1U", "1l", "1L", "1ll", "1LL", "1ul", "1lu", "1uL", "1LLU", "1llu"] {
assert!(c23(text).is_ok(), "{text} is a constant in both compilers");
}
for text in ["1lL", "1Ll", "1uu", "1lul", "1z", "1uz", "1f", "1x", "1_000"] {
assert_eq!(c23(text), Err(IntError::InvalidSuffix), "{text} is not");
}
}
#[test]
fn a_bit_int_constant_has_the_narrowest_type_that_holds_it() {
let cases = [
("0wb", true, 2),
("1wb", true, 2),
("3wb", true, 3),
("42wb", true, 7),
("255wb", true, 9),
("0uwb", false, 1),
("1uwb", false, 1),
("255uwb", false, 8),
("256uwb", false, 9),
("0xffffffffffffffffuwb", false, 64),
];
for (text, signed, width) in cases {
let constant = c23(text).expect("a _BitInt constant");
assert_eq!(
constant.ty,
IntConstantType::BitInt { signed, width },
"{text} is the wrong width"
);
}
for text in ["1uwb", "1wbu", "1UWB", "1WBu", "1uWB"] {
assert!(c23(text).is_ok(), "{text} is a constant in clang");
}
for text in ["1wB", "1Wb", "1lwb", "1wbl", "1wbwb"] {
assert_eq!(c23(text), Err(IntError::InvalidSuffix), "{text} is not");
}
let older = integer("1wb", Std::C17, &linux()).expect("clang accepts it everywhere");
assert!(older.remarks.has(Remarks::BIT_INT));
}
#[test]
fn a_binary_constant_is_an_extension_before_c23() {
assert!(c23("0b1").expect("standard in C23").remarks.is_none());
let older = integer("0b1", Std::C17, &linux()).expect("both compilers accept it");
assert!(older.remarks.has(Remarks::BINARY));
}
#[test]
fn an_octal_constant_names_the_digit_that_is_not_one() {
assert_eq!(c23("08"), Err(IntError::InvalidOctalDigit));
assert_eq!(c23("0778"), Err(IntError::InvalidOctalDigit));
assert_eq!(c23("09"), Err(IntError::InvalidOctalDigit));
assert_eq!(c23("9").expect("decimal").value, 9);
}
#[test]
fn a_prefix_with_no_digits_after_it_is_not_a_constant() {
assert_eq!(c23("0x"), Err(IntError::NoDigits));
assert_eq!(c23("0b"), Err(IntError::NoDigits));
}
#[test]
fn a_constant_larger_than_any_type_is_refused_rather_than_wrapped() {
assert_eq!(c23("340282366920938463463374607431768211456"), Err(IntError::TooLarge));
assert_eq!(c23("0x100000000000000000000000000000000"), Err(IntError::TooLarge));
assert_eq!(c23("170141183460469231731687303715884105728"), Err(IntError::TooLarge));
assert_eq!(kind("0x80000000000000000000000000000000", Std::C23), IntKind::UInt128);
assert_eq!(kind("0xffffffffffffffffffffffffffffffff", Std::C23), IntKind::UInt128);
}
#[test]
fn a_floating_constant_is_handed_back_rather_than_refused() {
for text in ["1.0", ".5", "1.", "1e5", "1E-5", "1e", "0x1p3", "0x1.8p+1", "1.5e3"] {
assert_eq!(c23(text), Err(IntError::Floating), "{text} belongs to the other path");
}
assert_eq!(c23("08e5"), Err(IntError::Floating));
assert_eq!(c23("0xe5").expect("hex digits").value, 0xe5);
assert_eq!(c23("1f"), Err(IntError::InvalidSuffix));
}
#[test]
fn the_type_comes_from_the_target_and_not_from_the_host() {
let windows =
TargetInfo::new("x86_64-pc-windows-msvc".parse::<Triple>().expect("a known triple"));
let on_windows = integer("4294967295", Std::C23, &windows).expect("a constant");
assert_eq!(on_windows.ty, IntConstantType::Standard(IntKind::LongLong));
assert_eq!(kind("4294967295", Std::C23), IntKind::Long);
}
fn float(text: &str) -> Result<FloatConstant, FloatError> {
floating(text, Std::C23, &linux())
}
fn bits(text: &str) -> u128 {
float(text).expect("a valid constant").value.to_bits()
}
#[test]
fn a_constant_with_no_suffix_is_a_double() {
let constant = float("1.5").expect("a constant");
assert_eq!(constant.ty, FloatConstantType::Double);
assert!(!constant.imaginary);
assert!(constant.remarks.is_none());
assert_eq!(constant.value.to_bits(), 0x3ff8_0000_0000_0000);
assert_eq!(bits("0.1"), 0x3fb9_9999_9999_999a);
assert_eq!(bits(".5"), 0x3fe0_0000_0000_0000);
assert_eq!(bits("1."), 0x3ff0_0000_0000_0000);
assert_eq!(bits("1e5"), 0x40f8_6a00_0000_0000);
assert_eq!(bits("0x1p3"), 0x4020_0000_0000_0000);
assert_eq!(bits("08e5"), 0x4128_6a00_0000_0000);
}
#[test]
fn the_suffix_names_the_type_rather_than_narrowing_a_list() {
let cases = [
("1.0", FloatConstantType::Double),
("1.0f", FloatConstantType::Float),
("1.0F", FloatConstantType::Float),
("1.0l", FloatConstantType::LongDouble),
("1.0L", FloatConstantType::LongDouble),
("1.0d", FloatConstantType::Double),
("1.0q", FloatConstantType::Float128),
("1.0w", FloatConstantType::Float80),
("1.0f16", FloatConstantType::Float16),
("1.0F16", FloatConstantType::Float16),
("1.0f32", FloatConstantType::Float32),
("1.0f64", FloatConstantType::Float64),
("1.0f128", FloatConstantType::Float128),
("1.0f32x", FloatConstantType::Float32x),
("1.0F64x", FloatConstantType::Float64x),
];
for (text, ty) in cases {
assert_eq!(float(text).expect("a constant").ty, ty, "{text} has the wrong type");
}
}
#[test]
fn each_type_is_converted_in_the_format_the_target_has_for_it() {
assert_eq!(bits("0.1f"), 0x3dcc_cccd);
assert_eq!(bits("0.1f16"), 0x2e66);
assert_eq!(bits("0.1f32x"), 0x3fb9_9999_9999_999a);
assert_eq!(bits("0.1f64x"), 0x3ffb_cccc_cccc_cccc_cccd);
assert_eq!(bits("0.1w"), 0x3ffb_cccc_cccc_cccc_cccd);
assert_eq!(bits("0.1l"), 0x3ffb_cccc_cccc_cccc_cccd);
assert_eq!(bits("0.1q"), 0x3ffb_9999_9999_9999_9999_9999_9999_999a);
assert_eq!(bits("0.1f128"), 0x3ffb_9999_9999_9999_9999_9999_9999_999a);
assert_eq!(bits("1.0l"), 0x3fff_8000_0000_0000_0000);
}
#[test]
fn the_format_comes_from_the_target_and_not_from_the_host() {
let arm = floating("1.0l", Std::C23, &aarch64()).expect("a constant");
assert_eq!(arm.value.to_bits(), 0x3fff_0000_0000_0000_0000_0000_0000_0000);
assert_eq!(bits("1.0l"), 0x3fff_8000_0000_0000_0000);
let arm_wide = floating("0.1f64x", Std::C23, &aarch64()).expect("a constant");
assert_eq!(arm_wide.value.to_bits(), 0x3ffb_9999_9999_9999_9999_9999_9999_999a);
let windows =
TargetInfo::new("x86_64-pc-windows-msvc".parse::<Triple>().expect("a known triple"));
let on_windows = floating("1.0l", Std::C23, &windows).expect("a constant");
assert_eq!(on_windows.value.to_bits(), 0x3ff0_0000_0000_0000);
}
#[test]
fn the_case_rules_of_a_floating_suffix_are_not_uniform() {
for text in ["1.0f", "1.0F", "1.0L", "1.0Q", "1.0W", "1.0F32", "1.0f64x", "1.0F64x"] {
assert!(float(text).is_ok(), "{text} is a constant in gcc");
}
for text in ["1.0F32X", "1.0f32X", "1.0f16x", "1.0ff", "1.0fl", "1.0lf", "1.0fF", "1.0LL"] {
assert_eq!(float(text), Err(FloatError::InvalidSuffix), "{text} is not");
}
}
#[test]
fn an_imaginary_suffix_may_sit_on_either_side_of_the_type() {
for text in ["1.0i", "1.0j", "1.0I", "1.0J", "1.0if", "1.0fi", "1.0Li", "1.0iL", "1.0f16i"]
{
let constant = float(text).expect("a constant in gcc");
assert!(constant.imaginary, "{text} is imaginary");
assert!(constant.remarks.has(Remarks::IMAGINARY));
}
assert_eq!(float("1.0ii"), Err(FloatError::InvalidSuffix));
assert_eq!(float("1.0ij"), Err(FloatError::InvalidSuffix));
assert!(!float("1.0f").expect("a constant").imaginary);
}
#[test]
fn a_decimal_floating_constant_is_recognised_and_refused() {
for text in ["1.0df", "1.0dd", "1.0dl", "1.0DF", "1.0DD", "1.0DL"] {
assert_eq!(float(text), Err(FloatError::DecimalFloat), "{text} is a decimal float");
}
for text in ["1.0Df", "1.0dF", "1.0dD", "1.0Dl"] {
assert_eq!(float(text), Err(FloatError::InvalidSuffix), "{text} is neither");
}
let long_way = float("1.0d").expect("a GCC extension");
assert_eq!(long_way.ty, FloatConstantType::Double);
assert!(long_way.remarks.has(Remarks::DOUBLE_SUFFIX));
}
#[test]
fn a_type_the_target_does_not_have_is_refused_by_name() {
assert_eq!(float("1.0f128x"), Err(FloatError::UnsupportedType));
assert_eq!(floating("1.0w", Std::C23, &aarch64()), Err(FloatError::UnsupportedType));
assert!(float("1.0w").is_ok());
}
#[test]
fn a_hexadecimal_constant_needs_an_exponent_and_a_decimal_one_does_not() {
assert_eq!(float("0x1.8"), Err(FloatError::MissingExponent));
assert_eq!(bits("0x1.8p0"), 0x3ff8_0000_0000_0000);
assert_eq!(bits("0x.8p1"), 0x3ff0_0000_0000_0000);
assert_eq!(bits("1.5"), 0x3ff8_0000_0000_0000);
for text in ["1.0e", "1e+", "1e-", "0x1p", "0x1p+"] {
assert_eq!(float(text), Err(FloatError::NoExponentDigits), "{text} has no exponent");
}
assert_eq!(float("1.2.3"), Err(FloatError::TooManyPoints));
}
#[test]
fn an_integer_constant_is_handed_back_rather_than_refused() {
for text in ["1", "0", "0x10", "1u", "0777", "1wb", "0b1", "0xe5", "1f"] {
assert_eq!(float(text), Err(FloatError::Integer), "{text} belongs to the other path");
}
}
#[test]
fn a_value_past_the_range_of_its_type_is_still_a_constant() {
let large = float("1e400").expect("a constant gcc compiles");
assert!(large.value.is_infinite());
assert!(large.remarks.has(Remarks::OUT_OF_RANGE));
let small = float("1e-400").expect("a constant gcc compiles");
assert!(small.value.is_zero());
assert!(small.remarks.has(Remarks::TRUNCATED));
assert!(float("1e39f").expect("a constant").remarks.has(Remarks::OUT_OF_RANGE));
assert!(float("1e-46f").expect("a constant").remarks.has(Remarks::TRUNCATED));
assert!(float("1e-4951l").expect("a constant").remarks.has(Remarks::TRUNCATED));
let subnormal = float("1e-320").expect("a constant");
assert!(!subnormal.value.is_zero());
assert!(subnormal.remarks.is_none());
}
#[test]
fn the_dialect_decides_what_a_constant_is_worth_saying_about() {
let old = floating("0x1p3", Std::C89, &linux()).expect("gcc compiles it anyway");
assert!(old.remarks.has(Remarks::HEX_FLOAT));
assert!(floating("0x1p3", Std::C99, &linux()).expect("standard").remarks.is_none());
assert_eq!(bits("1'0.5"), 0x4025_0000_0000_0000);
assert_eq!(bits("1.0e1'0"), 0x4202_a05f_2000_0000);
assert!(float("0x1'0p0").expect("a C23 constant").remarks.is_none());
let older = floating("1'0.5", Std::C17, &linux()).expect("still converted");
assert!(older.remarks.has(Remarks::SEPARATORS));
for text in ["1.0q", "1.0w", "1.0f16", "1.0f32x"] {
let constant = floating(text, Std::C89, &linux()).expect("gcc accepts it in C89");
assert!(constant.remarks.has(Remarks::EXTENDED_SUFFIX), "{text} is not standard");
}
assert!(float("1.0f").expect("a constant").remarks.is_none());
}
}