1use std::{fmt::Debug, ops::Add};
2
3#[derive(
5 Clone,
6 Copy,
7 Default,
8 PartialEq,
9 Eq,
10 Hash,
11 PartialOrd,
12 Ord,
13 derive_more::From,
14 derive_more::Into,
15 derive_more::Display,
16)]
17#[repr(transparent)]
18#[display("#{_0:04X}")]
19#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
20pub struct ProgramCounter(u16);
21
22impl ProgramCounter {
23 #[deprecated(note = "Use the `+` operator instead.")]
27 pub fn offset(&self, offset: i32) -> Result<Self, InvalidOffset> {
28 *self + offset
29 }
30
31 #[deprecated(note = "Use the `+` operator instead.")]
35 pub fn offset_i16(&self, offset: i16) -> Result<Self, InvalidOffset> {
36 *self + offset
37 }
38}
39
40impl Add<i16> for ProgramCounter {
41 type Output = Result<Self, InvalidOffset>;
42
43 fn add(self, rhs: i16) -> Self::Output {
44 let self_i32 = i32::from(self.0);
45 let offset_i32 = i32::from(rhs);
46 self_i32
47 .checked_add(offset_i32)
48 .and_then(|it| u16::try_from(it).ok())
49 .map(Self)
50 .ok_or(InvalidOffset)
51 }
52}
53
54impl Add<i32> for ProgramCounter {
55 type Output = Result<Self, InvalidOffset>;
56
57 fn add(self, rhs: i32) -> Self::Output {
58 let self_i32 = i32::from(self.0);
59 self_i32
60 .checked_add(rhs)
61 .and_then(|it| u16::try_from(it).ok())
62 .map(Self)
63 .ok_or(InvalidOffset)
64 }
65}
66
67impl Add<u16> for ProgramCounter {
68 type Output = Result<Self, InvalidOffset>;
69
70 fn add(self, rhs: u16) -> Self::Output {
71 let self_u32 = u32::from(self.0);
72 let offeset_u32 = u32::from(rhs);
73 self_u32
74 .checked_add(offeset_u32)
75 .and_then(|it| u16::try_from(it).ok())
76 .map(Self)
77 .ok_or(InvalidOffset)
78 }
79}
80
81impl ProgramCounter {
82 pub const ZERO: Self = Self(0);
84
85 #[must_use]
87 pub const fn is_entry_point(&self) -> bool {
88 self.0 == 0
89 }
90
91 #[must_use]
93 pub fn into<T>(self) -> T
94 where
95 u16: Into<T>,
96 {
97 self.0.into()
98 }
99}
100
101impl Debug for ProgramCounter {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 write!(f, "ProgramCounter(#{:04X})", self.0)
104 }
105}
106
107#[derive(thiserror::Error, Debug, PartialEq, Eq)]
109#[error("Invalid PC Offset")]
110pub struct InvalidOffset;
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_entry_point() {
118 assert!(ProgramCounter::ZERO.is_entry_point());
119 assert!(!ProgramCounter::from(1).is_entry_point());
120 }
121
122 #[test]
123 fn test_offset() {
124 let pc = ProgramCounter::from(10);
125 assert_eq!(pc + 5, Ok(ProgramCounter::from(15)));
126 assert_eq!(pc + -5, Ok(ProgramCounter::from(5)));
127 assert_eq!(pc + i32::MAX, Err(InvalidOffset));
128 }
129
130 #[test]
131 fn test_offset_i16() {
132 let pc = ProgramCounter::from(u16::MAX - 10);
133 assert_eq!(pc + 5i16, Ok(ProgramCounter::from(u16::MAX - 5)));
134 assert_eq!(pc + -5i16, Ok(ProgramCounter::from(u16::MAX - 15)));
135 assert_eq!(pc + i16::MAX, Err(InvalidOffset));
136 }
137
138 #[test]
139 fn test_default() {
140 assert_eq!(ProgramCounter::default(), ProgramCounter::from(0));
141 }
142
143 #[test]
144 fn test_display() {
145 let pc = ProgramCounter::from(10);
146 assert_eq!(format!("{pc}"), "#000A");
147 }
148}