use std::fmt;
use crate::Atom;
use crate::typed::TypedError;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct KindName(Atom);
impl KindName {
pub fn new(name: &str) -> Result<KindName, TypedError> {
if Self::is_valid(name) {
Ok(KindName(Atom::new(name)))
} else {
Err(TypedError::BadKind {
kind: name.to_owned(),
})
}
}
#[must_use]
pub fn declared(name: &'static str) -> KindName {
debug_assert!(Self::is_valid(name), "`{name}` isn't a kind");
KindName(Atom::new(name))
}
#[must_use]
pub const fn is_valid(name: &str) -> bool {
let b = name.as_bytes();
if b.is_empty() || !b[0].is_ascii_lowercase() {
return false;
}
let mut i = 1;
while i < b.len() {
let c = b[i];
if !(c.is_ascii_lowercase() || c.is_ascii_digit() || c == b'_') {
return false;
}
i += 1;
}
true
}
#[must_use]
pub fn as_str(self) -> &'static str {
self.0.as_str()
}
#[must_use]
pub fn atom(self) -> Atom {
self.0
}
}
impl fmt::Debug for KindName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "KindName({:?})", self.as_str())
}
}
impl fmt::Display for KindName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl PartialOrd for KindName {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for KindName {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl PartialEq<str> for KindName {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<&str> for KindName {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RefKey(Box<str>);
impl RefKey {
pub fn new(id: &str) -> Result<RefKey, TypedError> {
if Self::is_valid(id) {
Ok(RefKey(id.into()))
} else {
Err(TypedError::BadId {
kind: None,
id: id.to_owned(),
})
}
}
#[must_use]
pub fn is_valid(id: &str) -> bool {
if id.is_empty() {
return false;
}
let mut ascii = true;
for &c in id.as_bytes() {
if !c.is_ascii() {
ascii = false;
break;
}
if c <= b' ' || c == 0x7f {
return false;
}
}
ascii || !id.chars().any(|c| c.is_whitespace() || c.is_control())
}
pub(crate) fn spelled(id: &str) -> RefKey {
RefKey(id.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Debug for RefKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RefKey({:?})", &*self.0)
}
}
impl fmt::Display for RefKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl PartialEq<str> for RefKey {
fn eq(&self, other: &str) -> bool {
&*self.0 == other
}
}
impl PartialEq<&str> for RefKey {
fn eq(&self, other: &&str) -> bool {
&*self.0 == *other
}
}
impl std::ops::Deref for KindName {
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for KindName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl PartialEq<String> for KindName {
fn eq(&self, other: &String) -> bool {
self.as_str() == other
}
}
impl std::ops::Deref for RefKey {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
impl AsRef<str> for RefKey {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::borrow::Borrow<str> for RefKey {
fn borrow(&self) -> &str {
&self.0
}
}
impl PartialEq<String> for RefKey {
fn eq(&self, other: &String) -> bool {
&*self.0 == other
}
}
impl PartialEq<RefKey> for String {
fn eq(&self, other: &RefKey) -> bool {
self.as_str() == &*other.0
}
}
impl PartialEq<RefKey> for str {
fn eq(&self, other: &RefKey) -> bool {
self == &*other.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_id_is_anything_but_empty_whitespace_or_controls() {
for ok in [
"a",
"images/logo.png",
"~!@#$%^&*()_+-=[]{};:'\",.<>?/|\\`",
"héllo",
"日本",
] {
assert!(RefKey::is_valid(ok), "{ok:?}");
}
for bad in [
"",
" ",
"a b",
"a\tb",
"a\nb",
"a\rb",
"a\x0bb",
"a\x0cb",
"a\0b",
"a\x1fb",
"a\x7fb",
"é b",
"é\u{85}",
"é\u{a0}",
"é\u{2003}",
"日本\u{9f}",
"a\u{200a}",
] {
assert!(!RefKey::is_valid(bad), "{bad:?}");
}
}
#[test]
fn the_ascii_path_agrees_with_the_character_rule() {
for byte in 0..=0x7f_u8 {
let id = format!("a{}b", char::from(byte));
let by_chars = !id.chars().any(|c| c.is_whitespace() || c.is_control());
assert_eq!(RefKey::is_valid(&id), by_chars, "{id:?}");
}
}
}