use core::fmt;
use core::str::FromStr;
use crate::error::Error;
macro_rules! id_enum {
(
$(#[$m:meta])*
$name:ident, default = $def:ident {
$( $(#[$vm:meta])* $v:ident = $id:literal => $s:literal ),* $(,)?
}
) => {
$(#[$m])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum $name {
$( $(#[$vm])* $v, )*
Other(i32),
}
impl $name {
pub const ALL: &'static [$name] = &[$($name::$v),*];
pub const fn from_id(id: i32) -> Self {
match id {
$( $id => $name::$v, )*
o => $name::Other(o),
}
}
pub const fn id(self) -> i32 {
match self {
$( $name::$v => $id, )*
$name::Other(o) => o,
}
}
pub const fn name(self) -> Option<&'static str> {
match self {
$( $name::$v => Some($s), )*
$name::Other(_) => None,
}
}
}
impl Default for $name {
fn default() -> Self {
$name::$def
}
}
impl From<i32> for $name {
fn from(id: i32) -> Self {
$name::from_id(id)
}
}
impl From<$name> for i32 {
fn from(v: $name) -> i32 {
v.id()
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.name() {
Some(n) => f.write_str(n),
None => write!(f, "{}", self.id()),
}
}
}
impl FromStr for $name {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
let key = normalize(s);
$( if normalize($s) == key { return Ok($name::$v); } )*
parse_id(s).map($name::from_id).ok_or_else(|| {
Error::Format(format!(concat!("invalid ", stringify!($name), " {:?}"), s))
})
}
}
};
}
fn normalize(s: &str) -> String {
s.chars()
.filter(|c| *c != '_' && *c != '-' && !c.is_whitespace())
.flat_map(char::to_lowercase)
.collect()
}
pub(crate) fn parse_id(s: &str) -> Option<i32> {
let t = s.trim();
if let Ok(v) = t.parse::<i32>() {
return Some(v);
}
let v = t.parse::<f64>().ok()?;
(v.is_finite() && v.fract() == 0.0 && v.abs() <= i32::MAX as f64).then_some(v as i32)
}
id_enum! {
ExcitationType, default = EFieldSoft {
EFieldSoft = 0 => "EFieldSoft",
EFieldHard = 1 => "EFieldHard",
HFieldSoft = 2 => "HFieldSoft",
HFieldHard = 3 => "HFieldHard",
PlaneWave = 10 => "PlaneWave",
}
}
impl ExcitationType {
pub const fn is_electric(self) -> bool {
matches!(
self,
ExcitationType::EFieldSoft | ExcitationType::EFieldHard
)
}
pub const fn is_magnetic(self) -> bool {
matches!(
self,
ExcitationType::HFieldSoft | ExcitationType::HFieldHard
)
}
}
id_enum! {
ProbeType, default = Voltage {
Voltage = 0 => "Voltage",
Current = 1 => "Current",
EField = 2 => "EField",
HField = 3 => "HField",
WaveguideVoltage = 10 => "WaveguideVoltage",
WaveguideCurrent = 11 => "WaveguideCurrent",
}
}
id_enum! {
DumpType, default = EField {
EField = 0 => "EField",
HField = 1 => "HField",
Current = 2 => "Current",
TotalCurrentDensity = 3 => "TotalCurrentDensity",
DField = 4 => "DField",
BField = 5 => "BField",
EFieldFd = 10 => "EFieldFD",
HFieldFd = 11 => "HFieldFD",
CurrentFd = 12 => "CurrentFD",
TotalCurrentDensityFd = 13 => "TotalCurrentDensityFD",
DFieldFd = 14 => "DFieldFD",
BFieldFd = 15 => "BFieldFD",
SarLocal = 20 => "SARLocal",
Sar1g = 21 => "SAR1g",
Sar10g = 22 => "SAR10g",
SarRaw = 29 => "SARRaw",
}
}
impl DumpType {
pub const fn is_frequency_domain(self) -> bool {
matches!(self.id(), 10..=15 | 20..=29)
}
}
id_enum! {
DumpMode, default = Node {
NoInterpolation = 0 => "NoInterpolation",
Node = 1 => "Node",
Cell = 2 => "Cell",
}
}
id_enum! {
DumpFileType, default = Vtk {
Vtk = 0 => "VTK",
Hdf5 = 1 => "HDF5",
}
}
id_enum! {
LumpedElementType, default = Parallel {
Parallel = 0 => "Parallel",
Series = 1 => "Series",
}
}
id_enum! {
AbcType, default = Mur1st {
Mur1st = 0 => "MUR_1ST",
Mur1stSa = 1 => "MUR_1ST_SA",
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
pub enum Axis {
#[default]
X,
Y,
Z,
}
impl Axis {
pub const ALL: [Axis; 3] = [Axis::X, Axis::Y, Axis::Z];
pub const fn from_index(i: usize) -> Option<Axis> {
match i {
0 => Some(Axis::X),
1 => Some(Axis::Y),
2 => Some(Axis::Z),
_ => None,
}
}
pub const fn index(self) -> usize {
self as usize
}
}
impl From<Axis> for usize {
fn from(a: Axis) -> usize {
a.index()
}
}
impl TryFrom<usize> for Axis {
type Error = Error;
fn try_from(i: usize) -> Result<Axis, Error> {
Axis::from_index(i).ok_or_else(|| Error::Invalid(format!("axis {i} is not 0, 1 or 2")))
}
}
impl TryFrom<i32> for Axis {
type Error = Error;
fn try_from(i: i32) -> Result<Axis, Error> {
usize::try_from(i)
.ok()
.and_then(Axis::from_index)
.ok_or_else(|| Error::Invalid(format!("axis {i} is not 0, 1 or 2")))
}
}
impl fmt::Display for Axis {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Axis::X => "x",
Axis::Y => "y",
Axis::Z => "z",
})
}
}
impl FromStr for Axis {
type Err = Error;
fn from_str(s: &str) -> Result<Axis, Error> {
let bad = || Error::Format(format!("invalid axis {s:?}"));
match s.trim().to_ascii_lowercase().as_str() {
"x" | "rho" | "r" => Ok(Axis::X),
"y" | "a" | "alpha" => Ok(Axis::Y),
"z" => Ok(Axis::Z),
t => parse_id(t)
.and_then(|i| Axis::try_from(i).ok())
.ok_or_else(bad),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ids_round_trip() {
for v in DumpType::ALL {
assert_eq!(DumpType::from_id(v.id()), *v);
assert_eq!(v.to_string().parse::<DumpType>().unwrap(), *v);
}
assert_eq!(DumpType::from_id(7), DumpType::Other(7));
assert_eq!(DumpType::Other(7).id(), 7);
assert_eq!("7".parse::<DumpType>().unwrap(), DumpType::Other(7));
assert_eq!(
"0.000000e+00".parse::<LumpedElementType>().unwrap(),
LumpedElementType::Parallel
);
assert_eq!(
"plane_wave".parse::<ExcitationType>().unwrap(),
ExcitationType::PlaneWave
);
assert_eq!("mur-1st-sa".parse::<AbcType>().unwrap(), AbcType::Mur1stSa);
assert!("bogus".parse::<ProbeType>().is_err());
assert!("1.5".parse::<ProbeType>().is_err());
assert!(DumpType::Sar10g.is_frequency_domain());
assert!(!DumpType::BField.is_frequency_domain());
assert!(ExcitationType::HFieldHard.is_magnetic());
assert_eq!(DumpMode::default(), DumpMode::Node);
}
#[test]
fn axes() {
assert_eq!(Axis::try_from(2i32).unwrap(), Axis::Z);
assert!(Axis::try_from(-1i32).is_err());
assert_eq!("2.000000e+00".parse::<Axis>().unwrap(), Axis::Z);
assert_eq!("y".parse::<Axis>().unwrap(), Axis::Y);
assert!("3".parse::<Axis>().is_err());
assert_eq!(Axis::Z.index(), 2);
}
}