use rucc_session::Std;
use rucc_target::TargetInfo;
use rucc_types::{IntKind, int_width};
#[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, Default)]
pub struct Remarks(u8);
impl Remarks {
pub const NONE: Remarks = Remarks(0);
pub const BINARY: Remarks = Remarks(1);
pub const SEPARATORS: Remarks = Remarks(2);
pub const BIT_INT: Remarks = Remarks(4);
pub const LONG_LONG: Remarks = Remarks(8);
pub const UNSIGNED: Remarks = Remarks(16);
#[inline]
#[must_use]
pub const fn has(self, other: Remarks) -> bool {
self.0 & other.0 == other.0
}
#[inline]
#[must_use]
pub const fn with(self, other: Remarks) -> Remarks {
Remarks(self.0 | other.0)
}
#[inline]
#[must_use]
pub const fn is_none(self) -> bool {
self.0 == 0
}
}
pub fn integer(text: &str, std: Std, target: &TargetInfo) -> Result<IntConstant, IntError> {
let bytes = text.as_bytes();
let (base, start) = base_of(bytes);
if 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 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)) => &[],
}
}
#[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 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);
}
}