Skip to main content

mf2_model/
id.rs

1//! Identities shared with the catalog and the runtime.
2
3/// A message id: which message of a build's catalogs. `tr!` and the
4/// generated module make them; an application compares, hashes and passes
5/// them, and never builds one from a number.
6///
7/// Inside, low 24 bits are an index into one catalog chunk's message table
8/// and the high 8 bits the chunk (plans/02-catalog-format.md §3) — the
9/// catalog's layout, which 1.x does not promise, so the methods that expose
10/// it are hidden. Until per-route chunking exists every message is in chunk 0.
11#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
12#[repr(transparent)]
13pub struct MsgId(u32);
14
15impl MsgId {
16    #[doc(hidden)]
17    /// Bits of the index part.
18    pub const INDEX_BITS: u32 = 24;
19
20    const INDEX_MASK: u32 = (1 << Self::INDEX_BITS) - 1;
21
22    #[doc(hidden)]
23    /// The id of message `index` in `chunk`; `None` if `index ≥ 2^24`.
24    pub const fn new(chunk: u8, index: u32) -> Option<MsgId> {
25        if index > Self::INDEX_MASK {
26            return None;
27        }
28        Some(MsgId(((chunk as u32) << Self::INDEX_BITS) | index))
29    }
30
31    #[doc(hidden)]
32    /// The id whose raw value is `raw` (every `u32` is a valid id).
33    pub const fn from_raw(raw: u32) -> MsgId {
34        MsgId(raw)
35    }
36
37    #[doc(hidden)]
38    /// The raw value: `chunk << 24 | index`.
39    pub const fn raw(self) -> u32 {
40        self.0
41    }
42
43    #[doc(hidden)]
44    /// The chunk (high 8 bits).
45    pub const fn chunk(self) -> u8 {
46        // Lossless: a u32 shifted right by 24 fits in 8 bits.
47        #[allow(clippy::cast_possible_truncation)]
48        let chunk = (self.0 >> Self::INDEX_BITS) as u8;
49        chunk
50    }
51
52    #[doc(hidden)]
53    /// The index within the chunk (low 24 bits).
54    pub const fn index(self) -> u32 {
55        self.0 & Self::INDEX_MASK
56    }
57}
58
59/// Text direction.
60///
61/// The catalog header uses `Ltr`/`Rtl` for a locale; resolved values (e.g.
62/// through `u:dir`) may be `Auto`.
63#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
64#[repr(u8)]
65pub enum Dir {
66    /// Left to right.
67    Ltr = 0,
68    /// Right to left.
69    Rtl = 1,
70    /// Determined from the content (first strong character).
71    Auto = 2,
72}
73
74#[cfg(test)]
75mod tests {
76    use super::{Dir, MsgId};
77
78    #[test]
79    fn bit_layout() {
80        let id = MsgId::new(0x12, 0x34_5678).expect("index fits");
81        assert_eq!(id.raw(), 0x1234_5678);
82        assert_eq!(id.chunk(), 0x12);
83        assert_eq!(id.index(), 0x34_5678);
84        assert_eq!(MsgId::from_raw(0x1234_5678), id);
85        assert_eq!(MsgId::INDEX_BITS, 24);
86    }
87
88    #[test]
89    fn index_bounds() {
90        assert!(MsgId::new(0, (1 << 24) - 1).is_some());
91        assert!(MsgId::new(0, 1 << 24).is_none());
92        assert!(MsgId::new(255, u32::MAX).is_none());
93        let max = MsgId::new(255, (1 << 24) - 1).expect("fits");
94        assert_eq!(max.raw(), u32::MAX);
95        assert_eq!((max.chunk(), max.index()), (255, 0xFF_FFFF));
96        let zero = MsgId::new(0, 0).expect("fits");
97        assert_eq!(zero.raw(), 0);
98    }
99
100    #[test]
101    fn order_is_chunk_then_index() {
102        let a = MsgId::new(0, 5).expect("fits");
103        let b = MsgId::new(0, 6).expect("fits");
104        let c = MsgId::new(1, 0).expect("fits");
105        assert!(a < b && b < c);
106    }
107
108    #[test]
109    fn dir_discriminants() {
110        assert_eq!(Dir::Ltr as u8, 0);
111        assert_eq!(Dir::Rtl as u8, 1);
112        assert_eq!(Dir::Auto as u8, 2);
113    }
114}