use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TypeTag {
String,
Bytes,
Int,
Float,
Bool,
List,
Dict,
Closure,
Duration,
Set,
}
impl TypeTag {
pub(crate) const fn tag(self) -> &'static str {
match self {
Self::String => "string",
Self::Bytes => "bytes",
Self::Int => "int",
Self::Float => "float",
Self::Bool => "bool",
Self::List => "list",
Self::Dict => "dict",
Self::Closure => "closure",
Self::Duration => "duration",
Self::Set => "set",
}
}
#[cfg(test)]
pub(crate) const ALL: &'static [Self] = &[
Self::String,
Self::Bytes,
Self::Int,
Self::Float,
Self::Bool,
Self::List,
Self::Dict,
Self::Closure,
Self::Duration,
Self::Set,
];
}
impl fmt::Display for TypeTag {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.tag())
}
}
#[cfg(test)]
pub(crate) fn tag_is_canonical(tag: &str) -> bool {
harn_builtin_meta::runtime_type_tags::ALL.contains(&tag)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Expected {
One(TypeTag),
Either(TypeTag, TypeTag),
ListOf(TypeTag),
}
impl Expected {
pub(crate) const STRING: Self = Self::One(TypeTag::String);
pub(crate) const BYTES: Self = Self::One(TypeTag::Bytes);
pub(crate) const INT: Self = Self::One(TypeTag::Int);
pub(crate) const BOOL: Self = Self::One(TypeTag::Bool);
pub(crate) const FLOAT: Self = Self::One(TypeTag::Float);
pub(crate) const LIST: Self = Self::One(TypeTag::List);
pub(crate) const DICT: Self = Self::One(TypeTag::Dict);
pub(crate) const CLOSURE: Self = Self::One(TypeTag::Closure);
pub(crate) const INT_OR_FLOAT: Self = Self::Either(TypeTag::Int, TypeTag::Float);
pub(crate) const STRING_LIST: Self = Self::ListOf(TypeTag::String);
pub(crate) const LIST_OR_SET: Self = Self::Either(TypeTag::List, TypeTag::Set);
pub(crate) const BYTES_OR_STRING: Self = Self::Either(TypeTag::Bytes, TypeTag::String);
pub(crate) const DURATION_OR_INT: Self = Self::Either(TypeTag::Duration, TypeTag::Int);
}
const fn article(tag: &str) -> &'static str {
match tag.as_bytes().first() {
Some(b'a' | b'e' | b'i' | b'o' | b'u') => "an",
_ => "a",
}
}
impl fmt::Display for Expected {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::One(tag) => write!(formatter, "{} {tag}", article(tag.tag())),
Self::Either(TypeTag::Bytes, right) => {
write!(formatter, "bytes or {} {right}", article(right.tag()))
}
Self::Either(left, right) => write!(
formatter,
"{} {left} or {} {right}",
article(left.tag()),
article(right.tag())
),
Self::ListOf(element) => write!(formatter, "a list<{element}>"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_variant_names_a_tag_type_of_returns() {
for tag in TypeTag::ALL {
assert!(
tag_is_canonical(tag.tag()),
"TypeTag::{tag:?} spells `{}`, which `type_of` never returns; \
use a tag from harn_builtin_meta::runtime_type_tags::ALL",
tag.tag()
);
}
}
#[test]
fn expected_reads_as_english() {
assert_eq!(Expected::STRING.to_string(), "a string");
assert_eq!(Expected::INT.to_string(), "an int");
assert_eq!(Expected::STRING_LIST.to_string(), "a list<string>");
assert_eq!(Expected::BYTES_OR_STRING.to_string(), "bytes or a string");
assert_eq!(
Expected::DURATION_OR_INT.to_string(),
"a duration or an int"
);
}
}