use crate::literal::LitStr;
use std::{fmt::Display, str::FromStr};
use proc_macro2::{Ident, Literal, Span, TokenStream};
#[doc(hidden)]
mod __private {
pub trait IntegerSeal {
fn is_signed() -> bool {
false
}
}
pub trait FloatSeal {}
impl IntegerSeal for i8 {
fn is_signed() -> bool {
true
}
}
impl IntegerSeal for i16 {
fn is_signed() -> bool {
true
}
}
impl IntegerSeal for i32 {
fn is_signed() -> bool {
true
}
}
impl IntegerSeal for i64 {
fn is_signed() -> bool {
true
}
}
impl IntegerSeal for i128 {
fn is_signed() -> bool {
true
}
}
impl IntegerSeal for isize {
fn is_signed() -> bool {
true
}
}
impl IntegerSeal for u8 {}
impl IntegerSeal for u16 {}
impl IntegerSeal for u32 {}
impl IntegerSeal for u64 {}
impl IntegerSeal for u128 {}
impl IntegerSeal for usize {}
impl FloatSeal for f32 {}
impl FloatSeal for f64 {}
}
pub trait FromLit: Sized {
fn from_lit(lit: Literal) -> Result<Self, TokenStream>;
#[inline]
fn from_ident(ident: Ident) -> Result<Self, TokenStream> {
Err(comperr::error(
ident.span(),
format!("expected a literal, found identifier `{ident}`"),
))
}
#[inline]
fn from_negative_lit(lit: Literal) -> Result<Self, TokenStream> {
Err(comperr::error(lit.span(), "unexpected negative literal"))
}
}
fn parse_int_raw(raw: &str) -> Option<(String, u32, Option<String>)> {
let suffixes = [
"i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
];
let mut s = raw;
let mut found_suffix: Option<String> = None;
for suffix in suffixes {
if let Some(stripped) = s.strip_suffix(suffix) {
s = stripped;
found_suffix = Some(suffix.to_string());
break;
}
}
let (digits, radix) = if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X"))
{
(rest, 16)
} else if let Some(rest) = s.strip_prefix("0o").or_else(|| s.strip_prefix("0O")) {
(rest, 8)
} else if let Some(rest) = s.strip_prefix("0b").or_else(|| s.strip_prefix("0B")) {
(rest, 2)
} else {
(s, 10)
};
let cleaned: String = if digits.contains('_') {
let s: String = digits.chars().filter(|c| *c != '_').collect();
if s.is_empty() {
return None;
}
s
} else {
if digits.is_empty() {
return None;
}
digits.to_string()
};
Some((cleaned, radix, found_suffix))
}
fn parse_float_raw(raw: &str) -> (String, Option<String>) {
let suffixes = ["f32", "f64"];
let mut s = raw;
let mut found_suffix: Option<String> = None;
for suffix in suffixes {
if let Some(stripped) = s.strip_suffix(suffix) {
s = stripped;
found_suffix = Some(suffix.to_string());
break;
}
}
let cleaned = if s.contains('_') {
s.chars().filter(|c| *c != '_').collect()
} else {
s.to_string()
};
(cleaned, found_suffix)
}
impl FromLit for String {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
crate::parse_lit(&lit)
}
}
impl FromLit for LitStr {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
let span = lit.span();
let value = crate::parse_lit(&lit)?;
Ok(LitStr::new(value, span))
}
}
pub struct LitInt<T: __private::IntegerSeal = i32> {
pub value: T,
pub span: Span,
pub suffix: Option<String>,
}
impl<T> LitInt<T>
where
T: __private::IntegerSeal,
{
pub fn new(value: T, span: Span, suffix: Option<String>) -> Self {
Self {
value,
span,
suffix,
}
}
pub fn value(&self) -> &T {
&self.value
}
pub fn span(&self) -> Span {
self.span
}
pub fn suffix(&self) -> Option<&str> {
self.suffix.as_deref()
}
}
impl<T> FromLit for LitInt<T>
where
T: TryFrom<u128> + TryFrom<i128> + __private::IntegerSeal,
<T as TryFrom<u128>>::Error: Display,
<T as TryFrom<i128>>::Error: Display,
{
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
let span = lit.span();
let raw = lit.to_string();
let (digits, radix, suffix) =
parse_int_raw(&raw).ok_or_else(|| comperr::error(span, "malformed integer literal"))?;
let value: T = if let Ok(n) = u128::from_str_radix(&digits, radix) {
T::try_from(n)
.map_err(|e| comperr::error(span, format!("integer out of range: {e}")))?
} else if let Ok(n) = i128::from_str_radix(&digits, radix) {
T::try_from(n)
.map_err(|e| comperr::error(span, format!("integer out of range: {e}")))?
} else {
return Err(comperr::error(span, "malformed integer literal"));
};
Ok(LitInt::new(value, span, suffix))
}
#[inline]
#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
fn from_negative_lit(lit: Literal) -> Result<Self, TokenStream> {
if !T::is_signed() {
return Err(comperr::error(
lit.span(),
"cannot negate an unsigned integer",
));
}
let span = lit.span();
let raw = lit.to_string();
let (digits, radix, suffix) =
parse_int_raw(&raw).ok_or_else(|| comperr::error(span, "malformed integer literal"))?;
let n = u128::from_str_radix(&digits, radix)
.map_err(|_| comperr::error(span, "malformed integer literal"))?;
let negated = if n == (i128::MIN as u128) {
i128::MIN
} else if n > (i128::MAX as u128) {
return Err(comperr::error(span, "integer out of range"));
} else {
-(n as i128)
};
let value = T::try_from(negated)
.map_err(|e| comperr::error(span, format!("integer out of range: {e}")))?;
Ok(LitInt::new(value, span, suffix))
}
}
macro_rules! impl_from_lit_int_signed {
($($t:ty),*) => {
$(impl FromLit for $t {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
Ok(LitInt::<$t>::from_lit(lit)?.value)
}
#[inline]
fn from_negative_lit(lit: Literal) -> Result<Self, TokenStream> {
Ok(LitInt::<$t>::from_negative_lit(lit)?.value)
}
})*
};
}
macro_rules! impl_from_lit_int_unsigned {
($($t:ty),*) => {
$(impl FromLit for $t {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
Ok(LitInt::<$t>::from_lit(lit)?.value)
}
#[inline]
fn from_negative_lit(lit: Literal) -> Result<Self, TokenStream> {
Err(comperr::error(lit.span(), "cannot negate an unsigned integer"))
}
})*
};
}
impl_from_lit_int_signed!(i8, i16, i32, i64, i128, isize);
impl_from_lit_int_unsigned!(u16, u32, u64, u128, usize);
impl FromLit for u8 {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
let raw = lit.to_string();
if raw.starts_with("b'") {
let span = lit.span();
let inner = raw
.strip_prefix("b'")
.and_then(|s| s.strip_suffix('\''))
.ok_or_else(|| comperr::error(span, "expected a byte literal"))?;
let bytes = crate::unescape_bytes(inner, span)?;
if bytes.len() != 1 {
return Err(comperr::error(span, "invalid byte literal"));
}
return Ok(bytes[0]);
}
Ok(*LitInt::<u8>::from_lit(lit)?.value())
}
#[inline]
fn from_negative_lit(lit: Literal) -> Result<Self, TokenStream> {
Err(comperr::error(
lit.span(),
"cannot negate an unsigned integer",
))
}
}
pub struct LitFloat<T: __private::FloatSeal = f64> {
pub value: T,
pub span: Span,
pub suffix: Option<String>,
}
impl<T> LitFloat<T>
where
T: __private::FloatSeal,
{
pub fn new(value: T, span: Span, suffix: Option<String>) -> Self {
Self {
value,
span,
suffix,
}
}
pub fn value(&self) -> &T {
&self.value
}
pub fn span(&self) -> Span {
self.span
}
pub fn suffix(&self) -> Option<&str> {
self.suffix.as_deref()
}
}
impl<T> FromLit for LitFloat<T>
where
T: __private::FloatSeal + std::ops::Neg<Output = T>,
T: FromStr,
T::Err: Display,
{
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
let span = lit.span();
let raw = lit.to_string();
let (cleaned, suffix) = parse_float_raw(&raw);
let value = cleaned
.parse::<T>()
.map_err(|e| comperr::error(span, format!("malformed float literal: {e}")))?;
Ok(LitFloat::new(value, span, suffix))
}
#[inline]
fn from_negative_lit(lit: Literal) -> Result<Self, TokenStream> {
let span = lit.span();
let raw = lit.to_string();
let (cleaned, suffix) = parse_float_raw(&raw);
let value = cleaned
.parse::<T>()
.map_err(|e| comperr::error(span, format!("litext: malformed float literal: {e}")))?;
Ok(LitFloat::new(-value, span, suffix))
}
}
macro_rules! impl_from_lit_float {
($($t:ty),*) => {
$(impl FromLit for $t {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
Ok(LitFloat::<$t>::from_lit(lit)?.value)
}
#[inline]
fn from_negative_lit(lit: Literal) -> Result<Self, TokenStream> {
Ok(LitFloat::<$t>::from_negative_lit(lit)?.value)
}
})*
};
}
impl_from_lit_float!(f32, f64);
pub struct LitBool {
pub value: bool,
pub span: Span,
}
impl LitBool {
#[must_use]
pub fn new(value: bool, span: Span) -> Self {
Self { value, span }
}
#[must_use]
pub fn value(&self) -> bool {
self.value
}
#[must_use]
pub fn span(&self) -> Span {
self.span
}
}
impl FromLit for LitBool {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
Err(comperr::error(
lit.span(),
"expected `true` or `false`, found a literal",
))
}
#[inline]
fn from_ident(ident: Ident) -> Result<Self, TokenStream> {
let span = ident.span();
match ident.to_string().as_str() {
"true" => Ok(LitBool::new(true, span)),
"false" => Ok(LitBool::new(false, span)),
other => Err(comperr::error(
span,
format!("expected `true` or `false`, found `{other}`"),
)),
}
}
}
impl FromLit for bool {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
Err(comperr::error(
lit.span(),
"expected `true` or `false`, found a literal",
))
}
#[inline]
fn from_ident(ident: Ident) -> Result<Self, TokenStream> {
let span = ident.span();
match ident.to_string().as_str() {
"true" => Ok(true),
"false" => Ok(false),
other => Err(comperr::error(
span,
format!("expected `true` or `false`, found `{other}`"),
)),
}
}
}
pub struct LitChar {
pub value: char,
pub span: Span,
}
impl LitChar {
#[must_use]
pub fn new(value: char, span: Span) -> Self {
Self { value, span }
}
#[must_use]
pub fn value(&self) -> char {
self.value
}
#[must_use]
pub fn span(&self) -> Span {
self.span
}
}
impl FromLit for LitChar {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
let span = lit.span();
let raw = lit.to_string();
let Some(inner) = raw.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) else {
return Err(comperr::error(span, "expected a char literal"));
};
let unescaped = crate::unescape(inner, span)?;
if unescaped.chars().count() != 1 {
return Err(comperr::error(span, "invalid char literal"));
}
Ok(LitChar::new(unescaped.chars().next().unwrap(), span))
}
}
impl FromLit for char {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
Ok(LitChar::from_lit(lit)?.value)
}
}
use std::ffi::{CStr, CString};
fn parse_byte_str_raw(raw: &str, span: Span) -> Result<Vec<u8>, TokenStream> {
if raw.starts_with("br") {
let rest = &raw[1..];
let inner = crate::parse_raw(rest)
.ok_or_else(|| comperr::error(span, "litext: malformed raw byte string literal"))?;
return Ok(inner.into_bytes());
}
if let Some(inner) = raw.strip_prefix("b\"").and_then(|s| s.strip_suffix('"')) {
return crate::unescape_bytes(inner, span);
}
Err(comperr::error(
span,
"litext: expected a byte string literal",
))
}
fn parse_c_str_raw(raw: &str, span: Span) -> Result<Vec<u8>, TokenStream> {
if raw.starts_with("cr") {
let rest = &raw[1..];
let inner = crate::parse_raw(rest)
.ok_or_else(|| comperr::error(span, "litext: malformed raw C string literal"))?;
return Ok(inner.into_bytes());
}
if let Some(inner) = raw.strip_prefix("c\"").and_then(|s| s.strip_suffix('"')) {
let unescaped = crate::unescape(inner, span)?;
return Ok(unescaped.into_bytes());
}
Err(comperr::error(span, "litext: expected a C string literal"))
}
pub struct LitByteStr {
pub value: Vec<u8>,
pub span: Span,
}
impl LitByteStr {
#[must_use]
pub fn new(value: Vec<u8>, span: Span) -> Self {
Self { value, span }
}
#[must_use]
pub fn value(&self) -> &[u8] {
&self.value
}
#[must_use]
pub fn span(&self) -> Span {
self.span
}
}
impl FromLit for LitByteStr {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
let span = lit.span();
let raw = lit.to_string();
let bytes = parse_byte_str_raw(&raw, span)?;
Ok(LitByteStr::new(bytes, span))
}
}
impl FromLit for Vec<u8> {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
Ok(LitByteStr::from_lit(lit)?.value)
}
}
pub struct LitByte {
pub value: u8,
pub span: Span,
}
impl LitByte {
#[must_use]
pub fn new(value: u8, span: Span) -> Self {
Self { value, span }
}
#[must_use]
pub fn value(&self) -> u8 {
self.value
}
#[must_use]
pub fn span(&self) -> Span {
self.span
}
}
impl FromLit for LitByte {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
let span = lit.span();
let raw = lit.to_string();
let Some(inner) = raw.strip_prefix("b'").and_then(|s| s.strip_suffix('\'')) else {
return Err(comperr::error(span, "expected a byte literal"));
};
let bytes = crate::unescape_bytes(inner, span)?;
if bytes.len() != 1 {
return Err(comperr::error(span, "invalid byte literal"));
}
Ok(LitByte::new(bytes[0], span))
}
}
pub struct LitCStr {
pub value: CString,
pub span: Span,
}
impl LitCStr {
#[must_use]
pub fn new(value: CString, span: Span) -> Self {
Self { value, span }
}
#[must_use]
pub fn value(&self) -> &CStr {
&self.value
}
#[must_use]
pub fn span(&self) -> Span {
self.span
}
}
impl FromLit for LitCStr {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
let span = lit.span();
let raw = lit.to_string();
let bytes = parse_c_str_raw(&raw, span)?;
let cstring = CString::new(bytes).map_err(|_| {
comperr::error(
span,
"litext: C string literal contains an interior null byte",
)
})?;
Ok(LitCStr::new(cstring, span))
}
}
impl FromLit for CString {
#[inline]
fn from_lit(lit: Literal) -> Result<Self, TokenStream> {
Ok(LitCStr::from_lit(lit)?.value)
}
}