1use serde::{Deserialize, Serialize};
11use std::fmt;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
18pub enum Ternary {
19 False,
21 Unknown,
23 True,
25}
26
27impl Ternary {
28 pub const FALSE: Self = Self::False;
30 pub const UNKNOWN: Self = Self::Unknown;
32 pub const TRUE: Self = Self::True;
34
35 pub const ALL: [Self; 3] = [Self::False, Self::Unknown, Self::True];
37
38 pub const fn from_bool(value: bool) -> Self {
40 if value { Self::True } else { Self::False }
41 }
42
43 pub const fn to_bool(self) -> Option<bool> {
45 match self {
46 Self::True => Some(true),
47 Self::False => Some(false),
48 Self::Unknown => None,
49 }
50 }
51
52 pub const fn is_binary(self) -> bool {
54 !matches!(self, Self::Unknown)
55 }
56
57 pub const fn as_f64(self) -> f64 {
62 match self {
63 Self::False => 0.0,
64 Self::Unknown => 0.5,
65 Self::True => 1.0,
66 }
67 }
68
69 pub const fn is_false(self) -> bool {
74 matches!(self, Self::False)
75 }
76
77 pub const fn is_true(self) -> bool {
81 matches!(self, Self::True)
82 }
83
84 pub const fn is_unknown(self) -> bool {
86 matches!(self, Self::Unknown)
87 }
88
89 pub const fn is_known(self) -> bool {
91 !matches!(self, Self::Unknown)
92 }
93
94 pub const fn to_bool_strict(self) -> bool {
96 matches!(self, Self::True)
97 }
98
99 pub const fn to_bool_loose(self) -> bool {
101 !matches!(self, Self::False)
102 }
103
104 pub fn parse_char(c: char) -> Option<Self> {
106 match c {
107 '0' | 'f' | 'F' => Some(Self::False),
108 '1' | 't' | 'T' => Some(Self::True),
109 'x' | 'X' | '?' => Some(Self::Unknown),
110 _ => None,
111 }
112 }
113
114 pub const fn as_char(self) -> char {
116 match self {
117 Self::False => '0',
118 Self::Unknown => 'X',
119 Self::True => '1',
120 }
121 }
122
123 pub const fn as_compact_char(self) -> char {
125 match self {
126 Self::False => '0',
127 Self::Unknown => 'x',
128 Self::True => '1',
129 }
130 }
131}
132
133impl fmt::Display for Ternary {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 f.write_str(match self {
136 Self::False => "0",
137 Self::Unknown => "X",
138 Self::True => "1",
139 })
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn ordering_is_false_unknown_true() {
149 assert!(Ternary::False < Ternary::Unknown);
150 assert!(Ternary::Unknown < Ternary::True);
151 }
152
153 #[test]
154 fn parse_char_round_trips() {
155 for v in Ternary::ALL {
156 assert_eq!(Ternary::parse_char(v.as_char()), Some(v));
157 }
158 assert_eq!(Ternary::parse_char('?'), Some(Ternary::Unknown));
159 assert_eq!(Ternary::parse_char('z'), None);
160 }
161
162 #[test]
163 fn numeric_mapping() {
164 assert!((Ternary::False.as_f64() - 0.0).abs() < f64::EPSILON);
165 assert!((Ternary::Unknown.as_f64() - 0.5).abs() < f64::EPSILON);
166 assert!((Ternary::True.as_f64() - 1.0).abs() < f64::EPSILON);
167 }
168
169 #[test]
170 fn binarization() {
171 assert!(!Ternary::Unknown.to_bool_strict());
172 assert!(Ternary::Unknown.to_bool_loose());
173 assert!(Ternary::True.to_bool_strict());
174 assert!(Ternary::False.is_known());
175 assert!(!Ternary::Unknown.is_known());
176 }
177}