use crate::{Error, InternalString};
use alloc::borrow::{Borrow, Cow};
use alloc::format;
#[cfg(not(feature = "std"))]
use alloc::string::String;
use core::fmt;
use core::ops;
use core::str::FromStr;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Ident(InternalString);
impl Ident {
pub fn new<T>(ident: T) -> Ident
where
T: Into<InternalString>,
{
let ident = ident.into();
assert!(is_ident(&ident), "invalid identifier `{ident}`");
Ident(ident)
}
pub fn try_new<T>(ident: T) -> Result<Ident, Error>
where
T: Into<InternalString>,
{
let ident = ident.into();
if !is_ident(&ident) {
return Err(Error::new(format!("invalid identifier `{ident}`")));
}
Ok(Ident(ident))
}
pub fn new_sanitized<T>(ident: T) -> Self
where
T: AsRef<str>,
{
let input = ident.as_ref();
if input.is_empty() {
return Ident(InternalString::from("_"));
}
let mut ident = String::with_capacity(input.len());
for (i, ch) in input.chars().enumerate() {
if i == 0 && is_id_start(ch) {
ident.push(ch);
} else if is_id_continue(ch) {
if i == 0 {
ident.push('_');
}
ident.push(ch);
} else {
ident.push('_');
}
}
Ident(InternalString::from(ident))
}
#[inline]
pub fn new_unchecked<T>(ident: T) -> Self
where
T: Into<InternalString>,
{
Ident(ident.into())
}
#[inline]
#[must_use]
pub fn into_string(self) -> String {
self.0.into_string()
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl TryFrom<InternalString> for Ident {
type Error = Error;
#[inline]
fn try_from(s: InternalString) -> Result<Self, Self::Error> {
Ident::try_new(s)
}
}
impl TryFrom<String> for Ident {
type Error = Error;
#[inline]
fn try_from(s: String) -> Result<Self, Self::Error> {
Ident::try_new(s)
}
}
impl TryFrom<&str> for Ident {
type Error = Error;
#[inline]
fn try_from(s: &str) -> Result<Self, Self::Error> {
Ident::try_new(s)
}
}
impl<'a> TryFrom<Cow<'a, str>> for Ident {
type Error = Error;
#[inline]
fn try_from(s: Cow<'a, str>) -> Result<Self, Self::Error> {
Ident::try_new(s)
}
}
impl FromStr for Ident {
type Err = Error;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ident::try_new(s)
}
}
impl From<Ident> for InternalString {
#[inline]
fn from(ident: Ident) -> Self {
ident.0
}
}
impl From<Ident> for String {
#[inline]
fn from(ident: Ident) -> Self {
ident.into_string()
}
}
impl fmt::Debug for Ident {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Ident({self})")
}
}
impl fmt::Display for Ident {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl ops::Deref for Ident {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl AsRef<str> for Ident {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Borrow<str> for Ident {
#[inline]
fn borrow(&self) -> &str {
self.as_str()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Ident {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Ident {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let string = InternalString::deserialize(deserializer)?;
Ident::try_new(string).map_err(serde::de::Error::custom)
}
}
#[inline]
pub fn is_id_start(ch: char) -> bool {
unicode_ident::is_xid_start(ch) || ch == '_'
}
#[inline]
pub fn is_id_continue(ch: char) -> bool {
unicode_ident::is_xid_continue(ch) || ch == '-'
}
#[inline]
pub fn is_ident(s: &str) -> bool {
if s.is_empty() {
return false;
}
let mut chars = s.chars();
let first = chars.next().unwrap();
is_id_start(first) && chars.all(is_id_continue)
}