1use core::fmt;
2
3use alloc::format;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[repr(u8)]
9pub enum DefinitionTag {
10 Address = 0x01,
11 GlobalVar = 0x02,
12 ListDef = 0x03,
13 ListItem = 0x04,
14 ExternalFn = 0x05,
15 StructDef = 0x06,
25 LocalVar = 0x07,
27}
28
29impl DefinitionTag {
30 pub fn from_u8(byte: u8) -> Option<Self> {
32 match byte {
33 0x01 => Some(Self::Address),
34 0x02 => Some(Self::GlobalVar),
35 0x03 => Some(Self::ListDef),
36 0x04 => Some(Self::ListItem),
37 0x05 => Some(Self::ExternalFn),
38 0x06 => Some(Self::StructDef),
39 0x07 => Some(Self::LocalVar),
40 _ => None,
41 }
42 }
43}
44
45const HASH_MASK: u64 = (1 << 56) - 1;
47
48#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
52pub struct DefinitionId(u64);
53
54impl DefinitionId {
55 pub const RNG_CELL: DefinitionId =
76 DefinitionId(((DefinitionTag::GlobalVar as u64) << 56) | 0x00_5EED_0000_D1CE);
77
78 pub fn new(tag: DefinitionTag, hash: u64) -> Self {
82 let raw = (u64::from(tag as u8) << 56) | (hash & HASH_MASK);
83 Self(raw)
84 }
85
86 pub fn tag(self) -> DefinitionTag {
88 let byte = (self.0 >> 56) as u8;
91 DefinitionTag::from_u8(byte).unwrap_or(DefinitionTag::Address)
93 }
94
95 pub fn hash(self) -> u64 {
97 self.0 & HASH_MASK
98 }
99
100 pub fn to_raw(self) -> u64 {
102 self.0
103 }
104
105 pub fn from_raw(raw: u64) -> Option<Self> {
108 let byte = (raw >> 56) as u8;
109 DefinitionTag::from_u8(byte)?;
110 Some(Self(raw))
111 }
112}
113
114impl Serialize for DefinitionId {
115 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
116 serializer.serialize_str(&format!("{self}"))
118 }
119}
120
121impl<'de> Deserialize<'de> for DefinitionId {
122 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
123 let s = <&str>::deserialize(deserializer)?;
124 if !s.starts_with('$') || s.len() != 18 || s.as_bytes()[3] != b'_' {
126 return Err(serde::de::Error::custom(format!(
127 "invalid DefinitionId: {s:?}"
128 )));
129 }
130 let tag_byte = u8::from_str_radix(&s[1..3], 16).map_err(serde::de::Error::custom)?;
131 let tag = DefinitionTag::from_u8(tag_byte).ok_or_else(|| {
132 serde::de::Error::custom(format!("invalid tag byte: {tag_byte:#04x}"))
133 })?;
134 let hash = u64::from_str_radix(&s[4..], 16).map_err(serde::de::Error::custom)?;
135 Ok(Self::new(tag, hash))
136 }
137}
138
139impl fmt::Display for DefinitionId {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 write!(f, "${:02x}_{:014x}", self.tag() as u8, self.hash())
142 }
143}
144
145impl fmt::Debug for DefinitionId {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "{:?}({:#014x})", self.tag(), self.hash())
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
153pub struct NameId(pub u16);
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
157pub struct LineId {
158 pub container: DefinitionId,
159 pub index: u16,
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn roundtrip_raw() {
168 let id = DefinitionId::new(DefinitionTag::Address, 0xDEAD_BEEF);
169 let raw = id.to_raw();
170 let recovered = DefinitionId::from_raw(raw).unwrap();
171 assert_eq!(id, recovered);
172 }
173
174 #[test]
175 fn tag_extraction() {
176 for tag in [
177 DefinitionTag::Address,
178 DefinitionTag::GlobalVar,
179 DefinitionTag::ListDef,
180 DefinitionTag::ListItem,
181 DefinitionTag::ExternalFn,
182 DefinitionTag::StructDef,
183 DefinitionTag::LocalVar,
184 ] {
185 let id = DefinitionId::new(tag, 42);
186 assert_eq!(id.tag(), tag);
187 }
188 }
189
190 #[test]
191 fn struct_def_tag_roundtrips_through_from_u8() {
192 assert_eq!(DefinitionTag::from_u8(0x06), Some(DefinitionTag::StructDef));
193 }
194
195 #[test]
196 fn hash_masking() {
197 let id = DefinitionId::new(DefinitionTag::ListDef, u64::MAX);
199 assert_eq!(id.hash(), HASH_MASK);
200 assert_eq!(id.tag(), DefinitionTag::ListDef);
201 }
202
203 #[test]
204 fn invalid_tag_rejection() {
205 let raw = 0x00_DEAD_BEEF_CAFE_u64;
207 assert!(DefinitionId::from_raw(raw).is_none());
208
209 let raw = 0xFF_0000_0000_0000_u64;
211 assert!(DefinitionId::from_raw(raw).is_none());
212 }
213
214 #[test]
215 fn debug_format() {
216 let id = DefinitionId::new(DefinitionTag::ExternalFn, 0xCAFE);
217 let s = format!("{id:?}");
218 assert!(s.contains("ExternalFn"));
219 assert!(s.contains("0x"));
220 }
221
222 #[test]
223 fn rng_cell_is_a_well_formed_global_var_id() {
224 let id = DefinitionId::RNG_CELL;
228 assert_eq!(id.tag(), DefinitionTag::GlobalVar);
229 assert_eq!(id.hash(), 0x00_5EED_0000_D1CE);
230 assert_eq!(DefinitionId::from_raw(id.to_raw()), Some(id));
231 assert_eq!(
232 id,
233 DefinitionId::new(DefinitionTag::GlobalVar, 0x00_5EED_0000_D1CE)
234 );
235 }
236
237 #[test]
238 fn line_id_equality() {
239 let c = DefinitionId::new(DefinitionTag::Address, 1);
240 let a = LineId {
241 container: c,
242 index: 0,
243 };
244 let b = LineId {
245 container: c,
246 index: 0,
247 };
248 assert_eq!(a, b);
249 }
250}