use std::iter;
use strum::{EnumIter, EnumProperty, IntoEnumIterator, IntoStaticStr};
#[derive(Clone, Copy, Debug, EnumIter, EnumProperty, Eq, Hash, IntoStaticStr, PartialEq)]
#[cfg_attr(serde, derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(serde, serde(rename_all = "snake_case"))]
#[strum(serialize_all = "lowercase")]
pub enum Function {
#[cfg_attr(serde, serde(rename = "id"))]
#[strum(serialize = "id")]
Identity,
#[cfg_attr(serde, serde(rename = "bool"))]
#[strum(serialize = "bool")]
ToBool,
#[cfg_attr(serde, serde(rename = "int"))]
#[strum(serialize = "int")]
#[strum(props(alias = "integer"))]
ToInteger,
#[cfg_attr(serde, serde(rename = "float"))]
#[strum(serialize = "float")]
ToFloat,
Abs,
#[strum(props(alias = "fract"))]
Frac,
Trunc,
Floor,
Ceil,
Round,
#[cfg_attr(serde, serde(rename = "sqrt"))]
#[strum(serialize = "sqrt")]
SquareRoot,
#[cfg_attr(serde, serde(rename = "cbrt"))]
#[strum(serialize = "cbrt")]
CubeRoot,
Exp,
Ln,
#[cfg_attr(serde, serde(rename = "log2"))]
#[strum(serialize = "log2")]
Log2,
#[cfg_attr(serde, serde(rename = "log10"))]
#[strum(serialize = "log10")]
Log10,
#[cfg_attr(serde, serde(rename = "sin"))]
#[strum(serialize = "sin")]
Sine,
#[cfg_attr(serde, serde(rename = "cos"))]
#[strum(serialize = "cos")]
Cosine,
#[cfg_attr(serde, serde(rename = "tan"))]
#[strum(serialize = "tan")]
Tangent,
#[cfg(rng)]
Rand,
#[cfg(glam)]
#[cfg_attr(serde, serde(rename = "len"))]
#[strum(serialize = "len")]
#[strum(props(aliases = "length, mag, magnitude"))]
Length,
#[cfg(glam)]
#[cfg_attr(serde, serde(rename = "normalize"))]
#[strum(serialize = "normalize")]
#[strum(props(aliases = "normalize_or_zero"))]
NormalizeOrZero,
#[cfg(glam)]
#[cfg_attr(serde, serde(rename = "len_sq"))]
#[strum(serialize = "len_sq")]
#[strum(props(aliases = "len2, length_sq, length2, mag_sq, mag2, magnitude_sq, magnitude2"))]
LengthSquared,
#[cfg(glam)]
#[cfg_attr(serde, serde(rename = "dist"))]
#[strum(serialize = "dist")]
#[strum(props(alias = "distance"))]
Distance,
#[cfg(glam)]
#[cfg_attr(serde, serde(rename = "dist_sq"))]
#[strum(serialize = "dist_sq")]
#[strum(props(alias = "dist2, distance2, distance_sq"))]
DistanceSquared,
#[cfg(glam)]
#[cfg_attr(serde, serde(rename = "dot"))]
#[strum(serialize = "dot")]
DotProduct,
}
impl TryFrom<&str> for Function {
type Error = strum::ParseError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Self::iter()
.find(|f| {
Into::<&str>::into(f) == s || f.aliases().any(|a| a == s)
})
.ok_or(strum::ParseError::VariantNotFound)
}
}
impl TryFrom<String> for Function {
type Error = strum::ParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::try_from(s.as_str())
}
}
impl Function {
pub fn name(&self) -> &'static str {
self.into()
}
pub fn aliases(&self) -> impl Iterator<Item=&'static str> + '_ {
["alias", "aliases"].iter()
.filter_map(|prop| self.get_str(prop))
.flat_map(|aliases| aliases.split(','))
.map(str::trim)
}
pub fn names(&self) -> impl Iterator<Item=&'static str> + '_ {
iter::once(self.name()).chain(self.aliases())
}
}
#[cfg(test)]
mod checks {
use itertools::{Itertools, Position};
use super::*;
fn is_valid_identifier(s: &str) -> bool {
!s.is_empty() && s.chars().with_position().all(|(pos, c)| {
match pos {
Position::First | Position::Only => c.is_ascii_alphabetic() || c == '_',
_ => c.is_ascii_alphanumeric() || c == '_',
}
})
}
#[test]
fn names_are_valid() {
for func in Function::iter() {
assert!(
is_valid_identifier(func.name()),
"{:?} has an invalid name: `{}`", func, func.name());
}
}
#[test]
fn aliases_are_valid() {
for func in Function::iter() {
for alias in func.aliases() {
assert!(
is_valid_identifier(alias), "{:?} has an invalid alias: `{}`", func, alias);
}
}
}
#[test]
fn aliases_are_unique() {
for func in Function::iter() {
assert!(
func.aliases().all_unique(),
"{:?} has non-unique aliases: {}", func, func.aliases().duplicates().format(", "));
}
}
#[test]
fn aliases_are_distinct_from_name() {
for func in Function::iter() {
for alias in func.aliases() {
assert_ne!(
alias, func.name(), "`{}` is already the name of function {:?}", alias, func);
}
}
}
}