mokapot/jvm/code/
pc.rs

1use std::{fmt::Debug, ops::Add};
2
3/// Denotes a program counter in an instruction sequence.
4#[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    /// Creates a new program counter based on the given value with a given offset.
24    /// # Errors
25    /// - [`InvalidOffset`] If the resulting value is too large to fit into a [`ProgramCounter`].
26    #[deprecated(note = "Use the `+` operator instead.")]
27    pub fn offset(&self, offset: i32) -> Result<Self, InvalidOffset> {
28        *self + offset
29    }
30
31    /// Creates a new program counter based on the given value with a given offset (in [`i16`]).
32    /// # Errors
33    /// - [`InvalidOffset`] If the resulting value is too large to fit into a [`ProgramCounter`].
34    #[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    /// Denotes the entry point of a program.
83    pub const ZERO: Self = Self(0);
84
85    /// Checks if the program counter is an entry point.
86    #[must_use]
87    pub const fn is_entry_point(&self) -> bool {
88        self.0 == 0
89    }
90
91    /// Converts the program counter into a different type.
92    #[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/// An error occurring when trying to offset a program counter.
108#[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}