1use core::fmt;
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7#[repr(u8)]
8pub enum DefinitionTag {
9 Address = 0x01,
10 GlobalVar = 0x02,
11 ListDef = 0x03,
12 ListItem = 0x04,
13 ExternalFn = 0x05,
14 LocalVar = 0x07,
16}
17
18impl DefinitionTag {
19 pub fn from_u8(byte: u8) -> Option<Self> {
21 match byte {
22 0x01 => Some(Self::Address),
23 0x02 => Some(Self::GlobalVar),
24 0x03 => Some(Self::ListDef),
25 0x04 => Some(Self::ListItem),
26 0x05 => Some(Self::ExternalFn),
27 0x07 => Some(Self::LocalVar),
28 _ => None,
29 }
30 }
31}
32
33const HASH_MASK: u64 = (1 << 56) - 1;
35
36#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
40pub struct DefinitionId(u64);
41
42impl DefinitionId {
43 pub fn new(tag: DefinitionTag, hash: u64) -> Self {
47 let raw = (u64::from(tag as u8) << 56) | (hash & HASH_MASK);
48 Self(raw)
49 }
50
51 pub fn tag(self) -> DefinitionTag {
53 let byte = (self.0 >> 56) as u8;
56 DefinitionTag::from_u8(byte).unwrap_or(DefinitionTag::Address)
58 }
59
60 pub fn hash(self) -> u64 {
62 self.0 & HASH_MASK
63 }
64
65 pub fn to_raw(self) -> u64 {
67 self.0
68 }
69
70 pub fn from_raw(raw: u64) -> Option<Self> {
73 let byte = (raw >> 56) as u8;
74 DefinitionTag::from_u8(byte)?;
75 Some(Self(raw))
76 }
77}
78
79impl Serialize for DefinitionId {
80 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
81 serializer.serialize_str(&format!("{self}"))
83 }
84}
85
86impl<'de> Deserialize<'de> for DefinitionId {
87 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
88 let s = <&str>::deserialize(deserializer)?;
89 if !s.starts_with('$') || s.len() != 18 || s.as_bytes()[3] != b'_' {
91 return Err(serde::de::Error::custom(format!(
92 "invalid DefinitionId: {s:?}"
93 )));
94 }
95 let tag_byte = u8::from_str_radix(&s[1..3], 16).map_err(serde::de::Error::custom)?;
96 let tag = DefinitionTag::from_u8(tag_byte).ok_or_else(|| {
97 serde::de::Error::custom(format!("invalid tag byte: {tag_byte:#04x}"))
98 })?;
99 let hash = u64::from_str_radix(&s[4..], 16).map_err(serde::de::Error::custom)?;
100 Ok(Self::new(tag, hash))
101 }
102}
103
104impl fmt::Display for DefinitionId {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 write!(f, "${:02x}_{:014x}", self.tag() as u8, self.hash())
107 }
108}
109
110impl fmt::Debug for DefinitionId {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 write!(f, "{:?}({:#014x})", self.tag(), self.hash())
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
118pub struct NameId(pub u16);
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122pub struct LineId {
123 pub container: DefinitionId,
124 pub index: u16,
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn roundtrip_raw() {
133 let id = DefinitionId::new(DefinitionTag::Address, 0xDEAD_BEEF);
134 let raw = id.to_raw();
135 let recovered = DefinitionId::from_raw(raw).unwrap();
136 assert_eq!(id, recovered);
137 }
138
139 #[test]
140 fn tag_extraction() {
141 for tag in [
142 DefinitionTag::Address,
143 DefinitionTag::GlobalVar,
144 DefinitionTag::ListDef,
145 DefinitionTag::ListItem,
146 DefinitionTag::ExternalFn,
147 ] {
148 let id = DefinitionId::new(tag, 42);
149 assert_eq!(id.tag(), tag);
150 }
151 }
152
153 #[test]
154 fn hash_masking() {
155 let id = DefinitionId::new(DefinitionTag::ListDef, u64::MAX);
157 assert_eq!(id.hash(), HASH_MASK);
158 assert_eq!(id.tag(), DefinitionTag::ListDef);
159 }
160
161 #[test]
162 fn invalid_tag_rejection() {
163 let raw = 0x00_DEAD_BEEF_CAFE_u64;
165 assert!(DefinitionId::from_raw(raw).is_none());
166
167 let raw = 0xFF_0000_0000_0000_u64;
169 assert!(DefinitionId::from_raw(raw).is_none());
170 }
171
172 #[test]
173 fn debug_format() {
174 let id = DefinitionId::new(DefinitionTag::ExternalFn, 0xCAFE);
175 let s = format!("{id:?}");
176 assert!(s.contains("ExternalFn"));
177 assert!(s.contains("0x"));
178 }
179
180 #[test]
181 fn line_id_equality() {
182 let c = DefinitionId::new(DefinitionTag::Address, 1);
183 let a = LineId {
184 container: c,
185 index: 0,
186 };
187 let b = LineId {
188 container: c,
189 index: 0,
190 };
191 assert_eq!(a, b);
192 }
193}