use core::fmt;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum DefinitionTag {
Address = 0x01,
GlobalVar = 0x02,
ListDef = 0x03,
ListItem = 0x04,
ExternalFn = 0x05,
LocalVar = 0x07,
}
impl DefinitionTag {
pub fn from_u8(byte: u8) -> Option<Self> {
match byte {
0x01 => Some(Self::Address),
0x02 => Some(Self::GlobalVar),
0x03 => Some(Self::ListDef),
0x04 => Some(Self::ListItem),
0x05 => Some(Self::ExternalFn),
0x07 => Some(Self::LocalVar),
_ => None,
}
}
}
const HASH_MASK: u64 = (1 << 56) - 1;
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DefinitionId(u64);
impl DefinitionId {
pub fn new(tag: DefinitionTag, hash: u64) -> Self {
let raw = (u64::from(tag as u8) << 56) | (hash & HASH_MASK);
Self(raw)
}
pub fn tag(self) -> DefinitionTag {
let byte = (self.0 >> 56) as u8;
DefinitionTag::from_u8(byte).unwrap_or(DefinitionTag::Address)
}
pub fn hash(self) -> u64 {
self.0 & HASH_MASK
}
pub fn to_raw(self) -> u64 {
self.0
}
pub fn from_raw(raw: u64) -> Option<Self> {
let byte = (raw >> 56) as u8;
DefinitionTag::from_u8(byte)?;
Some(Self(raw))
}
}
impl Serialize for DefinitionId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&format!("{self}"))
}
}
impl<'de> Deserialize<'de> for DefinitionId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = <&str>::deserialize(deserializer)?;
if !s.starts_with('$') || s.len() != 18 || s.as_bytes()[3] != b'_' {
return Err(serde::de::Error::custom(format!(
"invalid DefinitionId: {s:?}"
)));
}
let tag_byte = u8::from_str_radix(&s[1..3], 16).map_err(serde::de::Error::custom)?;
let tag = DefinitionTag::from_u8(tag_byte).ok_or_else(|| {
serde::de::Error::custom(format!("invalid tag byte: {tag_byte:#04x}"))
})?;
let hash = u64::from_str_radix(&s[4..], 16).map_err(serde::de::Error::custom)?;
Ok(Self::new(tag, hash))
}
}
impl fmt::Display for DefinitionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "${:02x}_{:014x}", self.tag() as u8, self.hash())
}
}
impl fmt::Debug for DefinitionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}({:#014x})", self.tag(), self.hash())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NameId(pub u16);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LineId {
pub container: DefinitionId,
pub index: u16,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_raw() {
let id = DefinitionId::new(DefinitionTag::Address, 0xDEAD_BEEF);
let raw = id.to_raw();
let recovered = DefinitionId::from_raw(raw).unwrap();
assert_eq!(id, recovered);
}
#[test]
fn tag_extraction() {
for tag in [
DefinitionTag::Address,
DefinitionTag::GlobalVar,
DefinitionTag::ListDef,
DefinitionTag::ListItem,
DefinitionTag::ExternalFn,
] {
let id = DefinitionId::new(tag, 42);
assert_eq!(id.tag(), tag);
}
}
#[test]
fn hash_masking() {
let id = DefinitionId::new(DefinitionTag::ListDef, u64::MAX);
assert_eq!(id.hash(), HASH_MASK);
assert_eq!(id.tag(), DefinitionTag::ListDef);
}
#[test]
fn invalid_tag_rejection() {
let raw = 0x00_DEAD_BEEF_CAFE_u64;
assert!(DefinitionId::from_raw(raw).is_none());
let raw = 0xFF_0000_0000_0000_u64;
assert!(DefinitionId::from_raw(raw).is_none());
}
#[test]
fn debug_format() {
let id = DefinitionId::new(DefinitionTag::ExternalFn, 0xCAFE);
let s = format!("{id:?}");
assert!(s.contains("ExternalFn"));
assert!(s.contains("0x"));
}
#[test]
fn line_id_equality() {
let c = DefinitionId::new(DefinitionTag::Address, 1);
let a = LineId {
container: c,
index: 0,
};
let b = LineId {
container: c,
index: 0,
};
assert_eq!(a, b);
}
}