use alloc::{
borrow::Cow,
format,
string::{String, ToString},
};
use core::{fmt::Display, ops::Add, str::FromStr};
use thiserror::Error;
use crate::{
arg_err_noloc,
builtin::attributes::StringAttr,
combine::{Parser, token},
impl_printable_for_display,
parsable::{self, Parsable, ParseResult},
result::{self, Result},
utils::table::HMap,
};
#[derive(Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)]
pub struct Identifier(Cow<'static, str>);
impl Identifier {
pub const fn is_valid(s: &str) -> bool {
let b = s.as_bytes();
if b.is_empty() {
return false;
}
if !(b[0].is_ascii_alphabetic() || b[0] == b'_') {
return false;
}
let mut i = 1;
while i < b.len() {
if !(b[i].is_ascii_alphanumeric() || b[i] == b'_') {
return false;
}
i += 1;
}
true
}
pub fn try_new(value: String) -> Result<Self> {
if Identifier::is_valid(&value) {
Ok(Identifier(Cow::Owned(value)))
} else {
arg_err_noloc!(MalformedIdentifierErr(value.clone()))
}
}
#[track_caller]
pub const fn new(value: &'static str) -> Self {
if Identifier::is_valid(value) {
Identifier(Cow::Borrowed(value))
} else {
panic!("value is not a valid Identifier");
}
}
}
#[macro_export]
macro_rules! ident {
($value:literal) => {{
const IDENT: $crate::identifier::Identifier = $crate::identifier::Identifier::new($value);
IDENT
}};
}
impl Add for Identifier {
type Output = Identifier;
fn add(self, rhs: Self) -> Self::Output {
let mut result = self.0.into_owned();
result.push_str(rhs.as_ref());
Identifier(Cow::Owned(result))
}
}
impl_printable_for_display!(Identifier);
impl Display for Identifier {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.as_ref())
}
}
impl TryFrom<String> for Identifier {
type Error = result::Error;
fn try_from(value: String) -> Result<Self> {
Self::try_new(value)
}
}
impl TryFrom<&str> for Identifier {
type Error = result::Error;
fn try_from(value: &str) -> Result<Self> {
Self::try_new(value.to_string())
}
}
impl FromStr for Identifier {
type Err = result::Error;
fn from_str(value: &str) -> Result<Self> {
Self::try_new(value.to_string())
}
}
impl TryFrom<StringAttr> for Identifier {
type Error = result::Error;
fn try_from(value: StringAttr) -> Result<Self> {
Self::try_new(value.into())
}
}
impl From<Identifier> for String {
fn from(value: Identifier) -> Self {
value.0.into_owned()
}
}
impl AsRef<str> for Identifier {
fn as_ref(&self) -> &str {
&self.0
}
}
#[derive(Debug, Error)]
#[error("Malformed identifier {0}")]
struct MalformedIdentifierErr(String);
impl Parsable for Identifier {
type Arg = ();
type Parsed = Identifier;
fn parse<'a>(
state_stream: &mut parsable::StateStream<'a>,
_arg: Self::Arg,
) -> ParseResult<'a, Self::Parsed> {
use crate::combine::{many, parser::char};
let parser = (char::letter().or(token('_')))
.and(many::<String, _, _>(char::alpha_num().or(char::char('_'))))
.map(|(c, rest)| c.to_string() + &rest);
parser
.map(|str| {
str.try_into()
.expect("Something is wrong in our Identifier parser")
})
.parse_stream(state_stream)
.into()
}
}
#[derive(Default)]
pub struct Legaliser {
str_to_id: HMap<String, Identifier>,
rev_str_to_id: HMap<String, String>,
counter: usize,
}
impl Legaliser {
fn replace_illegal_chars(name: &str) -> String {
if TryInto::<Identifier>::try_into(name).is_ok() {
return name.to_string();
}
if name.is_empty() {
return String::from("_");
}
let mut char_iter = name.chars();
let first_char = char_iter.next().unwrap();
let mut result = if first_char.is_alphabetic() {
String::from(first_char)
} else {
String::from("_")
};
let rest = char_iter.map(|c| if c.is_ascii_alphanumeric() { c } else { '_' });
result.extend(rest);
result
}
pub fn legalise(&mut self, name: &str) -> Identifier {
if let Some(id) = self.str_to_id.get(name) {
return id.clone();
}
let legal_name = Self::replace_illegal_chars(name);
let mut legal_name_unique = legal_name.clone();
while self.rev_str_to_id.contains_key(&legal_name_unique) {
legal_name_unique = legal_name.clone() + &format!("_{}", self.counter);
self.counter += 1;
}
let legal_name_id = Identifier(Cow::Owned(legal_name_unique.clone()));
self.str_to_id
.insert(name.to_string(), legal_name_id.clone());
self.rev_str_to_id
.insert(legal_name_unique, name.to_string());
legal_name_id
}
pub fn source_name(&self, id: &Identifier) -> Option<String> {
self.rev_str_to_id.get(id.as_ref()).cloned()
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::cmp::Ordering;
#[test]
fn const_and_owned_are_equivalent() {
let konst = ident!("foo");
let owned = Identifier::try_new("foo".to_string()).unwrap();
assert_eq!(konst, owned);
assert_eq!(konst.cmp(&owned), Ordering::Equal);
assert!(ident!("z") > Identifier::try_new("a".to_string()).unwrap());
let mut map = HMap::<Identifier, u32>::default();
map.insert(owned.clone(), 42);
assert_eq!(map.get(&konst), Some(&42));
assert_eq!(format!("{konst:?}"), format!("{owned:?}"));
}
}