1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, num::NonZeroU32, str::FromStr};
5use std::error::Error;
6
7use use_component::ReferenceDesignator;
8
9pub mod prelude {
11 pub use crate::{
12 PinIdentifier, PinName, PinNameError, PinNumber, PinNumberError, PinPolarity,
13 PinPolarityParseError, PinRef, PinRole, PinRoleParseError,
14 };
15}
16
17#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
19pub struct PinNumber(NonZeroU32);
20
21impl PinNumber {
22 pub fn new(value: u32) -> Result<Self, PinNumberError> {
28 NonZeroU32::new(value).map(Self).ok_or(PinNumberError::Zero)
29 }
30
31 #[must_use]
33 pub const fn get(self) -> u32 {
34 self.0.get()
35 }
36}
37
38impl From<NonZeroU32> for PinNumber {
39 fn from(value: NonZeroU32) -> Self {
40 Self(value)
41 }
42}
43
44impl TryFrom<u32> for PinNumber {
45 type Error = PinNumberError;
46
47 fn try_from(value: u32) -> Result<Self, Self::Error> {
48 Self::new(value)
49 }
50}
51
52impl fmt::Display for PinNumber {
53 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
54 self.get().fmt(formatter)
55 }
56}
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub enum PinNumberError {
61 Zero,
63}
64
65impl fmt::Display for PinNumberError {
66 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
67 match self {
68 Self::Zero => formatter.write_str("pin number must be non-zero"),
69 }
70 }
71}
72
73impl Error for PinNumberError {}
74
75#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
77pub struct PinName(String);
78
79impl PinName {
80 pub fn new(value: impl AsRef<str>) -> Result<Self, PinNameError> {
86 let trimmed = value.as_ref().trim();
87 if trimmed.is_empty() {
88 Err(PinNameError::Empty)
89 } else {
90 Ok(Self(trimmed.to_string()))
91 }
92 }
93
94 #[must_use]
96 pub fn as_str(&self) -> &str {
97 &self.0
98 }
99
100 #[must_use]
102 pub fn into_string(self) -> String {
103 self.0
104 }
105}
106
107impl AsRef<str> for PinName {
108 fn as_ref(&self) -> &str {
109 self.as_str()
110 }
111}
112
113impl fmt::Display for PinName {
114 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115 formatter.write_str(self.as_str())
116 }
117}
118
119impl FromStr for PinName {
120 type Err = PinNameError;
121
122 fn from_str(value: &str) -> Result<Self, Self::Err> {
123 Self::new(value)
124 }
125}
126
127#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub enum PinNameError {
130 Empty,
132}
133
134impl fmt::Display for PinNameError {
135 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
136 match self {
137 Self::Empty => formatter.write_str("pin name cannot be empty"),
138 }
139 }
140}
141
142impl Error for PinNameError {}
143
144#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
146pub enum PinRole {
147 Input,
148 Output,
149 Bidirectional,
150 Power,
151 Ground,
152 Clock,
153 Reset,
154 Enable,
155 NoConnect,
156 Unknown,
157 Custom(String),
158}
159
160impl fmt::Display for PinRole {
161 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
162 formatter.write_str(match self {
163 Self::Input => "input",
164 Self::Output => "output",
165 Self::Bidirectional => "bidirectional",
166 Self::Power => "power",
167 Self::Ground => "ground",
168 Self::Clock => "clock",
169 Self::Reset => "reset",
170 Self::Enable => "enable",
171 Self::NoConnect => "no-connect",
172 Self::Unknown => "unknown",
173 Self::Custom(value) => value.as_str(),
174 })
175 }
176}
177
178impl FromStr for PinRole {
179 type Err = PinRoleParseError;
180
181 fn from_str(value: &str) -> Result<Self, Self::Err> {
182 let trimmed = value.trim();
183 if trimmed.is_empty() {
184 return Err(PinRoleParseError::Empty);
185 }
186
187 match normalized_token(trimmed).as_str() {
188 "input" | "in" => Ok(Self::Input),
189 "output" | "out" => Ok(Self::Output),
190 "bidirectional" | "bidirectional-io" | "io" => Ok(Self::Bidirectional),
191 "power" => Ok(Self::Power),
192 "ground" | "gnd" => Ok(Self::Ground),
193 "clock" | "clk" => Ok(Self::Clock),
194 "reset" => Ok(Self::Reset),
195 "enable" => Ok(Self::Enable),
196 "no-connect" | "nc" => Ok(Self::NoConnect),
197 "unknown" => Ok(Self::Unknown),
198 _ => Ok(Self::Custom(trimmed.to_string())),
199 }
200 }
201}
202
203#[derive(Clone, Copy, Debug, Eq, PartialEq)]
205pub enum PinRoleParseError {
206 Empty,
208}
209
210impl fmt::Display for PinRoleParseError {
211 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
212 match self {
213 Self::Empty => formatter.write_str("pin role cannot be empty"),
214 }
215 }
216}
217
218impl Error for PinRoleParseError {}
219
220#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
222pub enum PinPolarity {
223 ActiveHigh,
224 ActiveLow,
225 NonInverting,
226 Inverting,
227 Unknown,
228}
229
230impl fmt::Display for PinPolarity {
231 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
232 formatter.write_str(match self {
233 Self::ActiveHigh => "active-high",
234 Self::ActiveLow => "active-low",
235 Self::NonInverting => "non-inverting",
236 Self::Inverting => "inverting",
237 Self::Unknown => "unknown",
238 })
239 }
240}
241
242impl FromStr for PinPolarity {
243 type Err = PinPolarityParseError;
244
245 fn from_str(value: &str) -> Result<Self, Self::Err> {
246 let trimmed = value.trim();
247 if trimmed.is_empty() {
248 return Err(PinPolarityParseError::Empty);
249 }
250
251 match normalized_token(trimmed).as_str() {
252 "active-high" => Ok(Self::ActiveHigh),
253 "active-low" => Ok(Self::ActiveLow),
254 "non-inverting" => Ok(Self::NonInverting),
255 "inverting" => Ok(Self::Inverting),
256 "unknown" => Ok(Self::Unknown),
257 _ => Err(PinPolarityParseError::Unknown),
258 }
259 }
260}
261
262#[derive(Clone, Copy, Debug, Eq, PartialEq)]
264pub enum PinPolarityParseError {
265 Empty,
267 Unknown,
269}
270
271impl fmt::Display for PinPolarityParseError {
272 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
273 match self {
274 Self::Empty => formatter.write_str("pin polarity cannot be empty"),
275 Self::Unknown => formatter.write_str("unknown pin polarity"),
276 }
277 }
278}
279
280impl Error for PinPolarityParseError {}
281
282#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
284pub enum PinIdentifier {
285 Number(PinNumber),
286 Name(PinName),
287}
288
289impl From<PinNumber> for PinIdentifier {
290 fn from(value: PinNumber) -> Self {
291 Self::Number(value)
292 }
293}
294
295impl From<PinName> for PinIdentifier {
296 fn from(value: PinName) -> Self {
297 Self::Name(value)
298 }
299}
300
301impl fmt::Display for PinIdentifier {
302 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
303 match self {
304 Self::Number(number) => number.fmt(formatter),
305 Self::Name(name) => name.fmt(formatter),
306 }
307 }
308}
309
310#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
312pub struct PinRef {
313 component: ReferenceDesignator,
314 pin: PinIdentifier,
315}
316
317impl PinRef {
318 #[must_use]
320 pub const fn new(component: ReferenceDesignator, pin: PinIdentifier) -> Self {
321 Self { component, pin }
322 }
323
324 #[must_use]
326 pub const fn numbered(component: ReferenceDesignator, pin: PinNumber) -> Self {
327 Self::new(component, PinIdentifier::Number(pin))
328 }
329
330 #[must_use]
332 pub const fn named(component: ReferenceDesignator, pin: PinName) -> Self {
333 Self::new(component, PinIdentifier::Name(pin))
334 }
335
336 #[must_use]
338 pub const fn component(&self) -> &ReferenceDesignator {
339 &self.component
340 }
341
342 #[must_use]
344 pub const fn pin(&self) -> &PinIdentifier {
345 &self.pin
346 }
347}
348
349impl fmt::Display for PinRef {
350 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
351 write!(formatter, "{}:{}", self.component, self.pin)
352 }
353}
354
355fn normalized_token(value: &str) -> String {
356 value.trim().to_ascii_lowercase().replace(['_', ' '], "-")
357}
358
359#[cfg(test)]
360mod tests {
361 use super::{
362 PinName, PinNameError, PinNumber, PinNumberError, PinPolarity, PinRole, PinRoleParseError,
363 };
364
365 #[test]
366 fn accepts_valid_pin_numbers() -> Result<(), PinNumberError> {
367 let number = PinNumber::new(1)?;
368
369 assert_eq!(number.get(), 1);
370 assert_eq!(number.to_string(), "1");
371 Ok(())
372 }
373
374 #[test]
375 fn rejects_zero_pin_numbers() {
376 assert_eq!(PinNumber::new(0), Err(PinNumberError::Zero));
377 }
378
379 #[test]
380 fn accepts_valid_pin_names() -> Result<(), PinNameError> {
381 let name = PinName::new("RESET")?;
382
383 assert_eq!(name.as_str(), "RESET");
384 assert_eq!(name.to_string(), "RESET");
385 Ok(())
386 }
387
388 #[test]
389 fn rejects_empty_pin_names() {
390 assert_eq!(PinName::new(" "), Err(PinNameError::Empty));
391 }
392
393 #[test]
394 fn displays_and_parses_pin_roles() -> Result<(), PinRoleParseError> {
395 assert_eq!("input".parse::<PinRole>()?, PinRole::Input);
396 assert_eq!("NC".parse::<PinRole>()?, PinRole::NoConnect);
397 assert_eq!(PinRole::Power.to_string(), "power");
398 Ok(())
399 }
400
401 #[test]
402 fn displays_and_parses_pin_polarity() -> Result<(), Box<dyn std::error::Error>> {
403 assert_eq!("active low".parse::<PinPolarity>()?, PinPolarity::ActiveLow);
404 assert_eq!(PinPolarity::NonInverting.to_string(), "non-inverting");
405 Ok(())
406 }
407}