use crate::error::Error;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Identifier(String);
impl Identifier {
pub fn new(name: impl Into<String>) -> Result<Self, Error> {
let name = name.into();
valid_identifier(&name)
.then_some(Self(name.clone()))
.ok_or(Error::InvalidIdentifier {
attempted: name,
reason: "must match [$_A-Za-z][$_A-Za-z0-9]*",
})
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for Identifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PrivateIdentifier(String);
impl PrivateIdentifier {
pub fn new(name: impl Into<String>) -> Result<Self, Error> {
let name = name.into();
valid_identifier(&name)
.then_some(Self(name.clone()))
.ok_or(Error::InvalidIdentifier {
attempted: name,
reason: "private identifier must match [$_A-Za-z][$_A-Za-z0-9]* after the leading `#`",
})
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for PrivateIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "#{}", self.0)
}
}
fn valid_identifier(name: &str) -> bool {
let bytes = name.as_bytes();
if bytes.first().is_some_and(|&b| is_ident_start(b)) {
bytes.iter().skip(1).all(|&b| is_ident_continue(b))
} else {
false
}
}
fn is_ident_start(b: u8) -> bool {
b.is_ascii_alphabetic() || b == b'_' || b == b'$'
}
fn is_ident_continue(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_' || b == b'$'
}