1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7pub mod prelude {
9 pub use crate::{
10 BjtKind, BjtKindParseError, FetKind, FetKindParseError, TerminalParseError, TransistorKind,
11 TransistorSpec, TransistorTerminal,
12 };
13}
14
15#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
17pub enum BjtKind {
18 Npn,
19 Pnp,
20}
21
22impl fmt::Display for BjtKind {
23 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24 formatter.write_str(match self {
25 Self::Npn => "npn",
26 Self::Pnp => "pnp",
27 })
28 }
29}
30
31impl FromStr for BjtKind {
32 type Err = BjtKindParseError;
33
34 fn from_str(value: &str) -> Result<Self, Self::Err> {
35 let trimmed = value.trim();
36 if trimmed.is_empty() {
37 return Err(BjtKindParseError::Empty);
38 }
39
40 match trimmed.to_ascii_lowercase().as_str() {
41 "npn" => Ok(Self::Npn),
42 "pnp" => Ok(Self::Pnp),
43 _ => Err(BjtKindParseError::Unknown),
44 }
45 }
46}
47
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub enum BjtKindParseError {
51 Empty,
53 Unknown,
55}
56
57impl fmt::Display for BjtKindParseError {
58 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
60 Self::Empty => formatter.write_str("BJT kind cannot be empty"),
61 Self::Unknown => formatter.write_str("unknown BJT kind"),
62 }
63 }
64}
65
66impl Error for BjtKindParseError {}
67
68#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
70pub enum FetKind {
71 Nmos,
72 Pmos,
73 JfetN,
74 JfetP,
75}
76
77impl fmt::Display for FetKind {
78 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79 formatter.write_str(match self {
80 Self::Nmos => "nmos",
81 Self::Pmos => "pmos",
82 Self::JfetN => "jfet-n",
83 Self::JfetP => "jfet-p",
84 })
85 }
86}
87
88impl FromStr for FetKind {
89 type Err = FetKindParseError;
90
91 fn from_str(value: &str) -> Result<Self, Self::Err> {
92 let trimmed = value.trim();
93 if trimmed.is_empty() {
94 return Err(FetKindParseError::Empty);
95 }
96
97 match normalized_token(trimmed).as_str() {
98 "nmos" => Ok(Self::Nmos),
99 "pmos" => Ok(Self::Pmos),
100 "jfet-n" => Ok(Self::JfetN),
101 "jfet-p" => Ok(Self::JfetP),
102 _ => Err(FetKindParseError::Unknown),
103 }
104 }
105}
106
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub enum FetKindParseError {
110 Empty,
112 Unknown,
114}
115
116impl fmt::Display for FetKindParseError {
117 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
118 match self {
119 Self::Empty => formatter.write_str("FET kind cannot be empty"),
120 Self::Unknown => formatter.write_str("unknown FET kind"),
121 }
122 }
123}
124
125impl Error for FetKindParseError {}
126
127#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
129pub enum TransistorKind {
130 Bjt(BjtKind),
131 Fet(FetKind),
132 Unknown,
133 Custom(String),
134}
135
136impl fmt::Display for TransistorKind {
137 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
138 match self {
139 Self::Bjt(kind) => write!(formatter, "bjt:{kind}"),
140 Self::Fet(kind) => write!(formatter, "fet:{kind}"),
141 Self::Unknown => formatter.write_str("unknown"),
142 Self::Custom(value) => formatter.write_str(value),
143 }
144 }
145}
146
147#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
149pub enum TransistorTerminal {
150 Base,
151 Collector,
152 Emitter,
153 Gate,
154 Drain,
155 Source,
156 Body,
157}
158
159impl fmt::Display for TransistorTerminal {
160 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
161 formatter.write_str(match self {
162 Self::Base => "base",
163 Self::Collector => "collector",
164 Self::Emitter => "emitter",
165 Self::Gate => "gate",
166 Self::Drain => "drain",
167 Self::Source => "source",
168 Self::Body => "body",
169 })
170 }
171}
172
173impl FromStr for TransistorTerminal {
174 type Err = TerminalParseError;
175
176 fn from_str(value: &str) -> Result<Self, Self::Err> {
177 let trimmed = value.trim();
178 if trimmed.is_empty() {
179 return Err(TerminalParseError::Empty);
180 }
181
182 match normalized_token(trimmed).as_str() {
183 "base" => Ok(Self::Base),
184 "collector" => Ok(Self::Collector),
185 "emitter" => Ok(Self::Emitter),
186 "gate" => Ok(Self::Gate),
187 "drain" => Ok(Self::Drain),
188 "source" => Ok(Self::Source),
189 "body" => Ok(Self::Body),
190 _ => Err(TerminalParseError::Unknown),
191 }
192 }
193}
194
195#[derive(Clone, Copy, Debug, Eq, PartialEq)]
197pub enum TerminalParseError {
198 Empty,
200 Unknown,
202}
203
204impl fmt::Display for TerminalParseError {
205 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
206 match self {
207 Self::Empty => formatter.write_str("transistor terminal cannot be empty"),
208 Self::Unknown => formatter.write_str("unknown transistor terminal"),
209 }
210 }
211}
212
213impl Error for TerminalParseError {}
214
215#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
217pub struct TransistorSpec {
218 kind: TransistorKind,
219}
220
221impl TransistorSpec {
222 #[must_use]
224 pub const fn new(kind: TransistorKind) -> Self {
225 Self { kind }
226 }
227
228 #[must_use]
230 pub fn kind(&self) -> TransistorKind {
231 self.kind.clone()
232 }
233}
234
235fn normalized_token(value: &str) -> String {
236 value.trim().to_ascii_lowercase().replace(['_', ' '], "-")
237}
238
239#[cfg(test)]
240mod tests {
241 use super::{BjtKind, FetKind, TransistorKind, TransistorSpec, TransistorTerminal};
242
243 #[test]
244 fn displays_and_parses_bjt_kinds() -> Result<(), Box<dyn std::error::Error>> {
245 assert_eq!("npn".parse::<BjtKind>()?, BjtKind::Npn);
246 assert_eq!(BjtKind::Pnp.to_string(), "pnp");
247 Ok(())
248 }
249
250 #[test]
251 fn displays_and_parses_fet_kinds() -> Result<(), Box<dyn std::error::Error>> {
252 assert_eq!("jfet n".parse::<FetKind>()?, FetKind::JfetN);
253 assert_eq!(FetKind::Pmos.to_string(), "pmos");
254 Ok(())
255 }
256
257 #[test]
258 fn displays_and_parses_transistor_terminals() -> Result<(), Box<dyn std::error::Error>> {
259 assert_eq!(
260 "collector".parse::<TransistorTerminal>()?,
261 TransistorTerminal::Collector
262 );
263 assert_eq!(TransistorTerminal::Gate.to_string(), "gate");
264 Ok(())
265 }
266
267 #[test]
268 fn creates_transistor_specs() {
269 let spec = TransistorSpec::new(TransistorKind::Fet(FetKind::Nmos));
270
271 assert_eq!(spec.kind(), TransistorKind::Fet(FetKind::Nmos));
272 }
273}