use std::{
fmt::Display,
ops::{Add, Deref},
};
use combine::{Parser, token};
use rustc_hash::FxHashMap;
use thiserror::Error;
use crate::{
builtin::attributes::StringAttr,
impl_printable_for_display,
parsable::{self, Parsable, ParseResult},
result::{self, Result},
verify_err_noloc,
};
#[derive(Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)]
pub struct Identifier(String);
impl Identifier {
pub fn try_new(value: String) -> Result<Self> {
let mut chars_iter = value.chars();
match chars_iter.next() {
Some(first_char) if (first_char.is_ascii_alphabetic() || first_char == '_') => {
if !chars_iter.all(|c| c.is_ascii_alphanumeric() || c == '_') {
return verify_err_noloc!(MalformedIdentifierErr(value.clone()));
}
}
_ => {
return verify_err_noloc!(MalformedIdentifierErr(value.clone()));
}
}
Ok(Identifier(value))
}
}
impl Add for Identifier {
type Output = Identifier;
fn add(self, rhs: Self) -> Self::Output {
Identifier(self.0 + &rhs.0)
}
}
impl_printable_for_display!(Identifier);
impl Display for Identifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl TryFrom<String> for Identifier {
type Error = result::Error;
fn try_from(value: String) -> std::result::Result<Self, Self::Error> {
Self::try_new(value)
}
}
impl TryFrom<&str> for Identifier {
type Error = result::Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
Self::try_new(value.to_string())
}
}
impl TryFrom<StringAttr> for Identifier {
type Error = result::Error;
fn try_from(value: StringAttr) -> std::result::Result<Self, Self::Error> {
Self::try_new(value.into())
}
}
impl From<Identifier> for String {
fn from(value: Identifier) -> Self {
value.0
}
}
impl Deref for Identifier {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub fn underscore() -> Identifier {
Identifier("_".to_string())
}
#[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 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: FxHashMap<String, Identifier>,
rev_str_to_id: FxHashMap<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(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.clone(), name.to_string());
legal_name_id
}
pub fn source_name(&self, id: &Identifier) -> Option<String> {
self.rev_str_to_id.get(&id.0).cloned()
}
}