use rucc_session::Std;
use rucc_target::TargetInfo;
use crate::remarks::Remarks;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Encoding {
Plain,
Wide,
Utf8,
Utf16,
Utf32,
}
impl Encoding {
#[must_use]
pub fn element_width(self, target: &TargetInfo) -> u32 {
match self {
Encoding::Plain | Encoding::Utf8 => 8,
Encoding::Wide => target.wchar_width,
Encoding::Utf16 => 16,
Encoding::Utf32 => 32,
}
}
#[must_use]
pub fn is_signed(self, target: &TargetInfo) -> bool {
match self {
Encoding::Plain => target.char_is_signed,
Encoding::Wide => target.wchar_is_signed,
Encoding::Utf8 | Encoding::Utf16 | Encoding::Utf32 => false,
}
}
#[must_use]
pub const fn prefix(self) -> &'static str {
match self {
Encoding::Plain => "",
Encoding::Wide => "L",
Encoding::Utf8 => "u8",
Encoding::Utf16 => "u",
Encoding::Utf32 => "U",
}
}
#[must_use]
pub fn read_prefix(text: &str) -> Encoding {
Encoding::read(text.as_bytes()).0
}
fn read(bytes: &[u8]) -> (Encoding, usize) {
match bytes {
[b'u', b'8', ..] => (Encoding::Utf8, 2),
[b'u', ..] => (Encoding::Utf16, 1),
[b'U', ..] => (Encoding::Utf32, 1),
[b'L', ..] => (Encoding::Wide, 1),
_ => (Encoding::Plain, 0),
}
}
fn since(self, character: bool, gnu: bool) -> Std {
match self {
Encoding::Plain | Encoding::Wide => Std::C89,
Encoding::Utf8 if character => Std::C23,
Encoding::Utf8 | Encoding::Utf16 | Encoding::Utf32 if gnu => Std::C99,
Encoding::Utf8 | Encoding::Utf16 | Encoding::Utf32 => Std::C11,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CharConstant {
pub value: i64,
pub encoding: Encoding,
pub remarks: Remarks,
}
impl CharConstant {
#[must_use]
pub fn spell(self) -> String {
let mut out = String::from(self.encoding.prefix());
out.push('\'');
match self.encoding {
Encoding::Plain | Encoding::Utf8 if !(-128..=255).contains(&self.value) => {
let bits = self.value as u32;
let mut writing = false;
for shift in [24, 16, 8, 0] {
let byte = (bits >> shift) as u8;
writing |= byte != 0;
if writing {
out.push_str(&format!("\\x{byte:02x}"));
}
}
}
Encoding::Plain | Encoding::Utf8 => {
let byte = self.value as u8;
escape(u32::from(byte), '\'', &mut out);
}
_ => escape(self.value as u32, '\'', &mut out),
}
out.push('\'');
out
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StringLiteral {
pub elements: Vec<u32>,
pub encoding: Encoding,
pub remarks: Remarks,
}
impl StringLiteral {
#[must_use]
pub fn bytes(&self, target: &TargetInfo) -> Vec<u8> {
let width = self.encoding.element_width(target) / 8;
let mut bytes = Vec::with_capacity((self.elements.len() + 1) * width as usize);
for element in self.elements.iter().copied().chain([0]) {
let taken = &element.to_le_bytes()[..width as usize];
if target.little_endian {
bytes.extend_from_slice(taken);
} else {
bytes.extend(taken.iter().rev());
}
}
bytes
}
#[must_use]
pub fn spell(&self) -> String {
let prefix = self.encoding.prefix();
let wide = !matches!(self.encoding, Encoding::Plain | Encoding::Utf8);
let mut out = String::from(prefix);
out.push('"');
let mut ran_on = false;
for &element in &self.elements {
match printable(element) {
Some(ch) => {
if ran_on && ch.is_ascii_hexdigit() {
out.push('"');
out.push(' ');
out.push_str(prefix);
out.push('"');
}
escape(element, '"', &mut out);
ran_on = false;
}
None if wide => {
out.push_str(&format!("\\x{element:x}"));
ran_on = true;
}
None => {
out.push_str(&format!("\\{element:03o}"));
ran_on = false;
}
}
}
out.push('"');
out
}
}
fn printable(element: u32) -> Option<char> {
match element {
0x20..=0x7e => char::from_u32(element),
_ => None,
}
}
fn escape(element: u32, quote: char, out: &mut String) {
match printable(element) {
Some(ch) if ch == quote || ch == '\\' => {
out.push('\\');
out.push(ch);
}
Some('?') if out.ends_with('?') => out.push_str("\\?"),
Some(ch) => out.push(ch),
None => out.push_str(&format!("\\x{element:x}")),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LiteralError {
NotALiteral,
Empty,
TooLong,
NoHexDigits,
IncompleteUcn,
InvalidUcn,
NamedUcn,
InvalidUtf8,
PrefixNotInDialect,
MixedEncodings,
}
impl LiteralError {
#[must_use]
pub const fn message(self) -> &'static str {
match self {
LiteralError::NotALiteral => "not a character constant or a string literal",
LiteralError::Empty => "empty character constant",
LiteralError::TooLong => "character constant too long for its type",
LiteralError::NoHexDigits => "\\x used with no following hex digits",
LiteralError::IncompleteUcn => "incomplete universal character name",
LiteralError::InvalidUcn => "not a valid universal character",
LiteralError::NamedUcn => "named universal character escapes are not supported yet",
LiteralError::InvalidUtf8 => "failure to convert the source to the execution charset",
LiteralError::PrefixNotInDialect => {
"this encoding prefix is not available in this dialect"
}
LiteralError::MixedEncodings => {
"unsupported non-standard concatenation of string literals"
}
}
}
}
pub fn character(
text: &str,
std: Std,
gnu: bool,
target: &TargetInfo,
) -> Result<CharConstant, LiteralError> {
let (encoding, body) = open(text, b'\'', std, gnu, true)?;
let width = encoding.element_width(target);
let mut reader = Reader { bytes: body, index: 0, std, remarks: Remarks::NONE };
let mut value: u64 = 0;
let mut count = 0u32;
while let Some(piece) = reader.next(width)? {
for element in piece.elements(width) {
value = (value << width) | u64::from(element);
count += 1;
}
}
let mut remarks = reader.remarks;
let type_width = if encoding == Encoding::Plain { 32 } else { width };
let capacity = type_width / width;
match count {
0 => return Err(LiteralError::Empty),
1 => {}
_ if encoding == Encoding::Utf8 => return Err(LiteralError::TooLong),
_ if count > capacity => remarks = remarks.with(Remarks::TOO_LONG),
_ => remarks = remarks.with(Remarks::MULTICHARACTER),
}
let (bits, signed) = if count == 1 {
(width, encoding.is_signed(target))
} else {
(type_width, encoding == Encoding::Plain || encoding.is_signed(target))
};
Ok(CharConstant { value: narrow(value, bits, signed), encoding, remarks })
}
pub fn string(
text: &str,
std: Std,
gnu: bool,
target: &TargetInfo,
) -> Result<StringLiteral, LiteralError> {
strings(std::slice::from_ref(&text), std, gnu, target)
}
pub fn strings(
texts: &[&str],
std: Std,
gnu: bool,
target: &TargetInfo,
) -> Result<StringLiteral, LiteralError> {
let mut bodies = Vec::with_capacity(texts.len());
let mut encoding = Encoding::Plain;
for text in texts {
let (found, body) = open(text, b'"', std, gnu, false)?;
if found != Encoding::Plain {
if encoding != Encoding::Plain && encoding != found {
return Err(LiteralError::MixedEncodings);
}
encoding = found;
}
bodies.push(body);
}
let width = encoding.element_width(target);
let mut elements = Vec::new();
let mut remarks = Remarks::NONE;
for body in bodies {
let mut reader = Reader { bytes: body, index: 0, std, remarks: Remarks::NONE };
while let Some(piece) = reader.next(width)? {
elements.extend(piece.elements(width));
}
remarks = remarks.with(reader.remarks);
}
Ok(StringLiteral { elements, encoding, remarks })
}
fn open(
text: &str,
quote: u8,
std: Std,
gnu: bool,
character: bool,
) -> Result<(Encoding, &[u8]), LiteralError> {
let bytes = text.as_bytes();
let (encoding, prefix) = Encoding::read(bytes);
if std < encoding.since(character, gnu) {
return Err(LiteralError::PrefixNotInDialect);
}
let rest = &bytes[prefix..];
match rest {
[first, .., last] if *first == quote && *last == quote => {
Ok((encoding, &rest[1..rest.len() - 1]))
}
_ => Err(LiteralError::NotALiteral),
}
}
fn narrow(value: u64, bits: u32, signed: bool) -> i64 {
let masked = if bits >= 64 { value } else { value & ((1u64 << bits) - 1) };
if signed && bits < 64 && masked >> (bits - 1) & 1 == 1 {
(masked | !((1u64 << bits) - 1)) as i64
} else {
masked as i64
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Piece {
Char(u32),
Value(u32),
}
impl Piece {
fn elements(self, width: u32) -> Vec<u32> {
let code = match self {
Piece::Value(value) => return vec![value],
Piece::Char(code) => code,
};
match width {
8 => {
let mut buffer = [0u8; 4];
let text = char::from_u32(code)
.map(|character| character.encode_utf8(&mut buffer).len())
.unwrap_or(0);
buffer[..text].iter().map(|&byte| u32::from(byte)).collect()
}
16 if code > 0xffff => {
let value = code - 0x1_0000;
vec![0xd800 + (value >> 10), 0xdc00 + (value & 0x3ff)]
}
_ => vec![code],
}
}
}
struct Reader<'a> {
bytes: &'a [u8],
index: usize,
std: Std,
remarks: Remarks,
}
impl Reader<'_> {
fn next(&mut self, width: u32) -> Result<Option<Piece>, LiteralError> {
let Some(&byte) = self.bytes.get(self.index) else {
return Ok(None);
};
self.index += 1;
if byte == b'\\' {
return self.escape(width).map(Some);
}
if byte < 0x80 {
return Ok(Some(Piece::Char(u32::from(byte))));
}
if width == 8 {
return Ok(Some(Piece::Value(u32::from(byte))));
}
let length = utf8_length(byte).ok_or(LiteralError::InvalidUtf8)?;
let end = self.index - 1 + length;
let text = self
.bytes
.get(self.index - 1..end)
.and_then(|slice| std::str::from_utf8(slice).ok())
.ok_or(LiteralError::InvalidUtf8)?;
let character = text.chars().next().ok_or(LiteralError::InvalidUtf8)?;
self.index = end;
Ok(Some(Piece::Char(character as u32)))
}
fn escape(&mut self, width: u32) -> Result<Piece, LiteralError> {
let Some(&byte) = self.bytes.get(self.index) else {
return Err(LiteralError::NotALiteral);
};
self.index += 1;
let simple = match byte {
b'n' => Some(0x0a),
b't' => Some(0x09),
b'r' => Some(0x0d),
b'a' => Some(0x07),
b'b' => Some(0x08),
b'f' => Some(0x0c),
b'v' => Some(0x0b),
b'\\' | b'\'' | b'"' | b'?' => Some(u32::from(byte)),
_ => None,
};
if let Some(value) = simple {
return Ok(Piece::Value(value));
}
match byte {
b'e' | b'E' => {
self.remarks = self.remarks.with(Remarks::NON_ISO_ESCAPE);
Ok(Piece::Value(0x1b))
}
b'0'..=b'7' => Ok(Piece::Value(self.octal(byte, width))),
b'x' => self.hex(width).map(Piece::Value),
b'u' | b'U' => self.ucn(byte).map(Piece::Char),
b'N' => Err(LiteralError::NamedUcn),
_ => {
self.remarks = self.remarks.with(Remarks::UNKNOWN_ESCAPE);
Ok(Piece::Value(u32::from(byte)))
}
}
}
fn octal(&mut self, first: u8, width: u32) -> u32 {
let mut value = u32::from(first - b'0');
for _ in 0..2 {
match self.bytes.get(self.index) {
Some(&byte @ b'0'..=b'7') => {
value = value * 8 + u32::from(byte - b'0');
self.index += 1;
}
_ => break,
}
}
self.fit(value, width, Remarks::OCTAL_ESCAPE_OUT_OF_RANGE)
}
fn hex(&mut self, width: u32) -> Result<u32, LiteralError> {
let mut value: u64 = 0;
let mut digits = 0;
while let Some(digit) = self.bytes.get(self.index).and_then(|&byte| hex_digit(byte)) {
value = value.saturating_mul(16).saturating_add(u64::from(digit));
digits += 1;
self.index += 1;
}
if digits == 0 {
return Err(LiteralError::NoHexDigits);
}
Ok(self.fit(
u32::try_from(value).unwrap_or(u32::MAX),
width,
Remarks::HEX_ESCAPE_OUT_OF_RANGE,
))
}
fn ucn(&mut self, marker: u8) -> Result<u32, LiteralError> {
if self.bytes.get(self.index) == Some(&b'{') {
return Err(LiteralError::NamedUcn);
}
let digits = if marker == b'u' { 4 } else { 8 };
let mut value: u32 = 0;
for _ in 0..digits {
let Some(digit) = self.bytes.get(self.index).and_then(|&byte| hex_digit(byte)) else {
return Err(LiteralError::IncompleteUcn);
};
value = value * 16 + digit;
self.index += 1;
}
let allowed_low = matches!(value, 0x24 | 0x40 | 0x60);
if (value < 0xa0 && !allowed_low) || (0xd800..=0xdfff).contains(&value) || value > 0x10ffff
{
return Err(LiteralError::InvalidUcn);
}
if self.std < Std::C99 {
self.remarks = self.remarks.with(Remarks::UCN);
}
Ok(value)
}
fn fit(&mut self, value: u32, width: u32, out_of_range: Remarks) -> u32 {
if width >= 32 {
return value;
}
let mask = (1u32 << width) - 1;
if value & !mask != 0 {
self.remarks = self.remarks.with(out_of_range);
}
value & mask
}
}
fn hex_digit(byte: u8) -> Option<u32> {
char::from(byte).to_digit(16)
}
fn utf8_length(byte: u8) -> Option<usize> {
match byte {
0x00..=0x7f => Some(1),
0xc2..=0xdf => Some(2),
0xe0..=0xef => Some(3),
0xf0..=0xf4 => Some(4),
_ => None,
}
}
#[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 windows() -> TargetInfo {
TargetInfo::new("x86_64-pc-windows-msvc".parse::<Triple>().expect("a known triple"))
}
fn arm() -> TargetInfo {
TargetInfo::new("aarch64-unknown-linux-gnu".parse::<Triple>().expect("a known triple"))
}
fn ch(text: &str) -> i64 {
character(text, Std::C23, false, &linux()).expect("a character constant").value
}
fn ch_remarks(text: &str) -> Remarks {
character(text, Std::C23, false, &linux()).expect("a character constant").remarks
}
fn ch_error(text: &str) -> LiteralError {
character(text, Std::C23, false, &linux()).expect_err("not a character constant")
}
fn str_elements(text: &str) -> Vec<u32> {
string(text, Std::C23, false, &linux()).expect("a string literal").elements
}
fn str_bytes(text: &str) -> Vec<u8> {
string(text, Std::C23, false, &linux()).expect("a string literal").bytes(&linux())
}
#[test]
fn the_ordinary_cases_are_the_characters_they_look_like() {
assert_eq!(ch("'a'"), 0x61);
assert_eq!(ch(r"'\n'"), 0x0a);
assert_eq!(ch(r"'\0'"), 0);
assert_eq!(ch(r"'\\'"), 0x5c);
assert_eq!(ch(r"'\''"), 0x27);
assert_eq!(ch(r#"'\"'"#), 0x22);
assert_eq!(ch(r"'\?'"), 0x3f);
assert_eq!(str_elements(r#""hi""#), vec![0x68, 0x69]);
}
#[test]
fn a_high_character_takes_the_sign_of_plain_char() {
assert_eq!(ch(r"'\xff'"), -1);
assert_eq!(ch(r"'\377'"), -1);
assert_eq!(character(r"'\xff'", Std::C23, false, &arm()).expect("a constant").value, 255);
assert_eq!(ch(r"u8'\xff'"), 255);
}
#[test]
fn an_escape_too_big_for_its_element_is_truncated_and_says_so() {
let out = character(r"'\x1ff'", Std::C23, false, &linux()).expect("a constant");
assert_eq!(out.value, -1);
assert!(out.remarks.has(Remarks::HEX_ESCAPE_OUT_OF_RANGE));
let out = character(r"'\400'", Std::C23, false, &linux()).expect("a constant");
assert_eq!(out.value, 0);
assert!(out.remarks.has(Remarks::OCTAL_ESCAPE_OUT_OF_RANGE));
assert!(!out.remarks.has(Remarks::HEX_ESCAPE_OUT_OF_RANGE));
assert!(!ch_remarks(r"L'\x1ff'").has(Remarks::HEX_ESCAPE_OUT_OF_RANGE));
assert_eq!(ch(r"L'\x1ff'"), 0x1ff);
}
#[test]
fn adjacent_literals_agree_on_one_encoding_or_none_at_all() {
let target = linux();
let wide = strings(&[r#"L"a""#, r#""b""#], Std::C23, false, &target).expect("a string");
assert_eq!(wide.encoding, Encoding::Wide);
assert_eq!(wide.elements, vec![0x61, 0x62]);
assert_eq!(wide.bytes(&target).len(), 12);
let other_way =
strings(&[r#""a""#, r#"L"b""#], Std::C23, false, &target).expect("a string");
assert_eq!(other_way.encoding, Encoding::Wide);
assert_eq!(other_way.bytes(&target).len(), 12);
let u8_run = strings(&[r#"u8"a""#, r#""b""#], Std::C23, false, &target).expect("a string");
assert_eq!(u8_run.encoding, Encoding::Utf8);
assert_eq!(u8_run.bytes(&target).len(), 3);
let mixed = strings(&[r#"L"a""#, r#""é""#], Std::C23, false, &target).expect("a string");
assert_eq!(mixed.elements, vec![0x61, 0xe9]);
for run in [[r#"u8"a""#, r#"u"b""#], [r#"u8"a""#, r#"L"b""#], [r#"u"a""#, r#"L"b""#]] {
assert_eq!(
strings(&run, Std::C23, false, &target).expect_err("two prefixes in one run"),
LiteralError::MixedEncodings
);
}
assert_eq!(
strings(&[r#""hi""#], Std::C23, false, &target).expect("a string").elements,
vec![0x68, 0x69]
);
}
#[test]
fn more_than_one_character_shifts_them_together() {
assert_eq!(ch("'ab'"), 0x6162);
assert_eq!(ch("'abc'"), 0x616263);
assert_eq!(ch("'abcd'"), 0x61626364);
assert_eq!(ch("'abcde'"), 0x62636465);
assert_eq!(ch(r"'\xff\xfe'"), 0xfffe);
assert_eq!(ch(r"'\xff\xff\xff\xff'"), -1);
assert_eq!(ch(r"'\x80\x00'"), 0x8000);
assert!(ch_remarks("'ab'").has(Remarks::MULTICHARACTER));
assert!(ch_remarks("'abcd'").has(Remarks::MULTICHARACTER));
assert!(ch_remarks("'abcde'").has(Remarks::TOO_LONG));
assert!(!ch_remarks("'abcde'").has(Remarks::MULTICHARACTER));
assert!(!ch_remarks("'a'").has(Remarks::MULTICHARACTER));
}
#[test]
fn a_prefixed_constant_holds_one_character_and_keeps_the_last() {
for text in [r"L'ab'", r"u'ab'", r"U'ab'"] {
let out = character(text, Std::C23, false, &linux()).expect("a constant");
assert_eq!(out.value, 0x62, "{text}");
assert!(out.remarks.has(Remarks::TOO_LONG), "{text}");
}
assert_eq!(ch_error("u8'ab'"), LiteralError::TooLong);
assert_eq!(ch_error("u8'é'"), LiteralError::TooLong);
}
#[test]
fn the_empty_constant_has_no_value_to_have() {
assert_eq!(ch_error("''"), LiteralError::Empty);
assert_eq!(ch_error("L''"), LiteralError::Empty);
assert_eq!(str_elements(r#""""#), Vec::<u32>::new());
assert_eq!(str_bytes(r#""""#), vec![0]);
}
#[test]
fn a_source_character_is_encoded_and_an_escape_is_not() {
assert_eq!(ch("'é'"), 0xc3a9);
assert_eq!(ch("L'é'"), 0xe9);
assert_eq!(ch("u'€'"), 0x20ac);
assert_eq!(ch(r"U'\U0001F600'"), 0x1f600);
assert_eq!(ch(r"'\U0001F600'"), i64::from(0xf09f_9880u32 as i32));
assert!(ch_remarks(r"'\U0001F600'").has(Remarks::MULTICHARACTER));
}
#[test]
fn the_escapes_outside_the_standard_still_have_values() {
assert_eq!(ch(r"'\e'"), 0x1b);
assert!(ch_remarks(r"'\e'").has(Remarks::NON_ISO_ESCAPE));
assert_eq!(ch(r"'\q'"), 0x71);
assert!(ch_remarks(r"'\q'").has(Remarks::UNKNOWN_ESCAPE));
assert_eq!(ch_error(r"'\x'"), LiteralError::NoHexDigits);
assert_eq!(ch_error(r"'\N{LATIN SMALL LETTER A}'"), LiteralError::NamedUcn);
}
#[test]
fn a_universal_character_name_may_not_name_just_anything() {
assert_eq!(ch("'\\u0024'"), 0x24);
assert_eq!(ch("'\\u00e9'"), 0xc3a9);
assert_eq!(ch_error("'\\u0041'"), LiteralError::InvalidUcn);
assert_eq!(ch_error(r"'\ud800'"), LiteralError::InvalidUcn);
assert_eq!(ch_error(r"'\u00'"), LiteralError::IncompleteUcn);
assert_eq!(ch_error(r"'\U00110000'"), LiteralError::InvalidUcn);
}
#[test]
fn a_universal_character_name_before_c99_is_worth_a_remark() {
let out = character("'\\u00e9'", Std::C89, false, &linux()).expect("a constant");
assert!(out.remarks.has(Remarks::UCN));
let out = character("'\\u00e9'", Std::C99, false, &linux()).expect("a constant");
assert!(!out.remarks.has(Remarks::UCN));
}
#[test]
fn an_octal_escape_ends_and_a_hex_escape_does_not() {
assert_eq!(str_elements(r#""\1234""#), vec![0x53, 0x34]);
assert_eq!(str_elements(r#""\x41z""#), vec![0x41, 0x7a]);
assert_eq!(str_elements(r#""\x41""#), vec![0x41]);
}
#[test]
fn a_string_is_as_many_bytes_as_its_encoding_makes_it() {
assert_eq!(str_bytes(r#""abc""#).len(), 4);
assert_eq!(str_bytes(r#"L"abc""#).len(), 16);
assert_eq!(str_bytes(r#"u"abc""#).len(), 8);
assert_eq!(str_bytes(r#"U"abc""#).len(), 16);
assert_eq!(str_bytes(r#"u8"abc""#).len(), 4);
assert_eq!(str_bytes(r#""a\0b""#), vec![0x61, 0x00, 0x62, 0x00]);
assert_eq!(str_bytes(r#""é""#), vec![0xc3, 0xa9, 0x00]);
}
#[test]
fn utf16_splits_the_characters_that_do_not_fit_into_a_surrogate_pair() {
assert_eq!(
str_elements(r#"u8"é€😀""#),
vec![0xc3, 0xa9, 0xe2, 0x82, 0xac, 0xf0, 0x9f, 0x98, 0x80]
);
assert_eq!(str_elements(r#"u"€😀""#), vec![0x20ac, 0xd83d, 0xde00]);
assert_eq!(str_elements(r#"U"€😀""#), vec![0x20ac, 0x1f600]);
}
#[test]
fn a_wide_literal_is_whatever_the_target_makes_wchar_t() {
let text = r#"L"a😀""#;
let here = string(text, Std::C23, false, &linux()).expect("a string");
assert_eq!(here.elements, vec![0x61, 0x1f600]);
assert_eq!(here.bytes(&linux()).len(), 12);
let there = string(text, Std::C23, false, &windows()).expect("a string");
assert_eq!(there.elements, vec![0x61, 0xd83d, 0xde00]);
assert_eq!(there.bytes(&windows()).len(), 8);
assert_eq!(
character(r"L'\xffffffff'", Std::C23, false, &linux()).expect("a constant").value,
-1
);
assert_eq!(
character(r"L'\xffffffff'", Std::C23, false, &arm()).expect("a constant").value,
0xffff_ffff
);
}
#[test]
fn the_bytes_come_out_in_the_targets_order() {
let mut big = linux();
big.little_endian = false;
let literal = string(r#"u"ab""#, Std::C23, false, &big).expect("a string");
assert_eq!(literal.bytes(&big), vec![0x00, 0x61, 0x00, 0x62, 0x00, 0x00]);
assert_eq!(literal.bytes(&linux()), vec![0x61, 0x00, 0x62, 0x00, 0x00, 0x00]);
}
#[test]
fn a_prefix_is_only_available_in_the_dialect_that_has_it() {
assert!(character("L'a'", Std::C89, false, &linux()).is_ok());
assert_eq!(
character("u'a'", Std::C99, false, &linux()).expect_err("not in C99"),
LiteralError::PrefixNotInDialect
);
assert!(character("u'a'", Std::C11, false, &linux()).is_ok());
assert!(string(r#"u8"a""#, Std::C11, false, &linux()).is_ok());
assert_eq!(
character("u8'a'", Std::C11, false, &linux()).expect_err("not in C11"),
LiteralError::PrefixNotInDialect
);
assert!(character("u8'a'", Std::C23, false, &linux()).is_ok());
}
#[test]
fn the_gnu_dialects_have_the_string_prefixes_earlier_and_the_character_one_at_the_same_time() {
assert!(string(r#"u8"a""#, Std::C99, true, &linux()).is_ok());
assert!(string(r#"u"a""#, Std::C99, true, &linux()).is_ok());
assert!(string(r#"U"a""#, Std::C99, true, &linux()).is_ok());
assert!(character("u'a'", Std::C99, true, &linux()).is_ok());
assert_eq!(
string(r#"u8"a""#, Std::C89, true, &linux()).expect_err("not in gnu89"),
LiteralError::PrefixNotInDialect
);
assert_eq!(
character("u8'a'", Std::C17, true, &linux()).expect_err("not in gnu17"),
LiteralError::PrefixNotInDialect
);
}
#[test]
fn an_element_is_as_wide_as_the_encoding_and_the_target_agree() {
let target = linux();
assert_eq!(Encoding::Plain.element_width(&target), 8);
assert_eq!(Encoding::Utf8.element_width(&target), 8);
assert_eq!(Encoding::Utf16.element_width(&target), 16);
assert_eq!(Encoding::Utf32.element_width(&target), 32);
assert_eq!(Encoding::Wide.element_width(&target), 32);
assert_eq!(Encoding::Wide.element_width(&windows()), 16);
assert!(Encoding::Plain.is_signed(&target));
assert!(!Encoding::Plain.is_signed(&arm()));
assert!(Encoding::Wide.is_signed(&target));
assert!(!Encoding::Wide.is_signed(&arm()));
assert!(!Encoding::Utf8.is_signed(&target));
assert!(!Encoding::Utf16.is_signed(&target));
assert!(!Encoding::Utf32.is_signed(&target));
}
#[test]
fn a_spelling_that_is_not_a_literal_is_refused_rather_than_guessed_at() {
assert_eq!(ch_error("a"), LiteralError::NotALiteral);
assert_eq!(ch_error("'a"), LiteralError::NotALiteral);
assert_eq!(
string("'a'", Std::C23, false, &linux()).expect_err("not a string"),
LiteralError::NotALiteral
);
assert_eq!(ch_error("'"), LiteralError::NotALiteral);
}
#[test]
fn every_error_has_something_to_print() {
for error in [
LiteralError::NotALiteral,
LiteralError::Empty,
LiteralError::TooLong,
LiteralError::NoHexDigits,
LiteralError::IncompleteUcn,
LiteralError::InvalidUcn,
LiteralError::NamedUcn,
LiteralError::InvalidUtf8,
LiteralError::PrefixNotInDialect,
LiteralError::MixedEncodings,
] {
assert!(!error.message().is_empty());
}
}
}