Skip to main content

dds_bridge/solver/
vulnerability.rs

1//! Vulnerability encoding for par calculation
2
3use thiserror::Error;
4
5use core::fmt;
6use core::str::FromStr;
7
8bitflags::bitflags! {
9    /// Vulnerability of pairs
10    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12    pub struct Vulnerability: u8 {
13        /// North-South are vulnerable
14        const NS = 1;
15        /// East-West are vulnerable
16        const EW = 2;
17    }
18}
19
20impl Vulnerability {
21    /// Both sides vulnerable
22    pub const ALL: Self = Self::all();
23
24    /// Neither side vulnerable
25    pub const NONE: Self = Self::empty();
26
27    /// Convert to encoding in [`dds_bridge_sys`]
28    #[must_use]
29    #[inline]
30    pub const fn to_sys(self) -> i32 {
31        const ALL: u8 = Vulnerability::all().bits();
32        const NS: u8 = Vulnerability::NS.bits();
33        const EW: u8 = Vulnerability::EW.bits();
34
35        match self.bits() {
36            0 => 0,
37            ALL => 1,
38            NS => 2,
39            EW => 3,
40            _ => unreachable!(),
41        }
42    }
43
44    /// Conditionally swap [`NS`](Self::NS) and [`EW`](Self::EW)
45    #[must_use]
46    #[inline]
47    pub const fn rotate(self, condition: bool) -> Self {
48        // The trick: multiplying by 0x55 duplicates bit 0 into bit 1 (and bit 1
49        // into bit 2, etc.).  Shifting right by 1 when `condition` is true
50        // effectively moves bit 0 → nowhere and bit 1 → bit 0, swapping NS/EW.
51        Self::from_bits_truncate((self.bits() * 0x55) >> (condition as u8))
52    }
53}
54
55/// Error returned when parsing a [`Vulnerability`] fails
56#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
57#[error("Invalid vulnerability: expected one of none, ns, ew, both")]
58pub struct ParseVulnerabilityError;
59
60impl FromStr for Vulnerability {
61    type Err = ParseVulnerabilityError;
62
63    fn from_str(s: &str) -> Result<Self, Self::Err> {
64        match s.to_ascii_lowercase().as_str() {
65            "none" => Ok(Self::NONE),
66            "ns" => Ok(Self::NS),
67            "ew" => Ok(Self::EW),
68            "both" | "all" => Ok(Self::ALL),
69            _ => Err(ParseVulnerabilityError),
70        }
71    }
72}
73
74impl fmt::Display for Vulnerability {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        f.write_str(match *self {
77            Self::NONE => "none",
78            Self::NS => "ns",
79            Self::EW => "ew",
80            Self::ALL => "both",
81            _ => unreachable!(),
82        })
83    }
84}
85
86/// Exhaustively check correctness of [`Vulnerability::rotate`] at compile time
87const _: () = {
88    const ALL: Vulnerability = Vulnerability::all();
89    const NONE: Vulnerability = Vulnerability::empty();
90
91    assert!(matches!(ALL.rotate(true), ALL));
92    assert!(matches!(NONE.rotate(true), NONE));
93    assert!(matches!(Vulnerability::NS.rotate(true), Vulnerability::EW));
94    assert!(matches!(Vulnerability::EW.rotate(true), Vulnerability::NS));
95
96    assert!(matches!(ALL.rotate(false), ALL));
97    assert!(matches!(NONE.rotate(false), NONE));
98    assert!(matches!(Vulnerability::NS.rotate(false), Vulnerability::NS));
99    assert!(matches!(Vulnerability::EW.rotate(false), Vulnerability::EW));
100};