Skip to main content

facet_value/
other.rs

1//! Extensible "Other" value types using tag 7 with a secondary discriminant.
2//!
3//! This module provides types that share tag 7 but are distinguished by a
4//! secondary `OtherKind` discriminant stored on the heap. This allows for
5//! unlimited future extensibility without consuming additional tag bits.
6//!
7//! Current types:
8//! - `VQName`: Qualified name (namespace + local name) for XML namespace support
9//! - `VUuid`: 128-bit UUID for preserving semantic identity
10
11#[cfg(feature = "alloc")]
12use alloc::alloc::{Layout, alloc, dealloc};
13use core::fmt::{self, Debug, Formatter};
14use core::hash::{Hash, Hasher};
15
16use crate::value::{TypeTag, Value};
17
18/// Secondary discriminant for "Other" types (tag 7).
19///
20/// This allows 256 subtypes to share a single tag value.
21#[repr(u8)]
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23#[non_exhaustive]
24pub enum OtherKind {
25    /// Qualified name (namespace + local name)
26    QName = 0,
27    /// UUID (128-bit universally unique identifier)
28    Uuid = 1,
29    /// Unicode scalar value (`char`)
30    Char = 2,
31}
32
33// ============================================================================
34// VQName - Qualified Name
35// ============================================================================
36
37/// Header for VQName values.
38///
39/// Layout: [kind: u8][_pad: 7 bytes][namespace: Value][local_name: Value]
40#[repr(C, align(8))]
41struct QNameHeader {
42    /// The OtherKind discriminant (always QName = 0)
43    kind: OtherKind,
44    /// Padding for alignment
45    _pad: [u8; 7],
46    /// Optional namespace (Value::NULL if none)
47    namespace: Value,
48    /// Local name (always a VString)
49    local_name: Value,
50}
51
52/// A qualified name consisting of an optional namespace and a local name.
53///
54/// `VQName` is used for XML namespace support, where elements and attributes
55/// can have qualified names like `{http://example.com}element`.
56///
57/// Both the namespace and local name are stored as `Value`s, allowing them
58/// to benefit from inline string optimization for short names.
59#[repr(transparent)]
60pub struct VQName(pub(crate) Value);
61
62impl VQName {
63    const fn layout() -> Layout {
64        Layout::new::<QNameHeader>()
65    }
66
67    #[cfg(feature = "alloc")]
68    fn alloc() -> *mut QNameHeader {
69        unsafe { alloc(Self::layout()).cast::<QNameHeader>() }
70    }
71
72    #[cfg(feature = "alloc")]
73    fn dealloc(ptr: *mut QNameHeader) {
74        unsafe {
75            dealloc(ptr.cast::<u8>(), Self::layout());
76        }
77    }
78
79    fn header(&self) -> &QNameHeader {
80        unsafe { &*(self.0.heap_ptr() as *const QNameHeader) }
81    }
82
83    /// Creates a new qualified name with a namespace and local name.
84    #[cfg(feature = "alloc")]
85    #[must_use]
86    pub fn new(namespace: impl Into<Value>, local_name: impl Into<Value>) -> Self {
87        unsafe {
88            let ptr = Self::alloc();
89            // Use ptr::write to avoid dropping uninitialized memory
90            core::ptr::write(&raw mut (*ptr).kind, OtherKind::QName);
91            core::ptr::write(&raw mut (*ptr)._pad, [0; 7]);
92            core::ptr::write(&raw mut (*ptr).namespace, namespace.into());
93            core::ptr::write(&raw mut (*ptr).local_name, local_name.into());
94            VQName(Value::new_ptr(ptr.cast(), TypeTag::Other))
95        }
96    }
97
98    /// Creates a new qualified name without a namespace.
99    #[cfg(feature = "alloc")]
100    #[must_use]
101    pub fn new_local(local_name: impl Into<Value>) -> Self {
102        Self::new(Value::NULL, local_name)
103    }
104
105    /// Returns the namespace, or `None` if there is no namespace.
106    #[must_use]
107    pub fn namespace(&self) -> Option<&Value> {
108        let ns = &self.header().namespace;
109        if ns.is_null() { None } else { Some(ns) }
110    }
111
112    /// Returns the local name.
113    #[must_use]
114    pub fn local_name(&self) -> &Value {
115        &self.header().local_name
116    }
117
118    /// Returns `true` if this qualified name has a namespace.
119    #[must_use]
120    pub fn has_namespace(&self) -> bool {
121        !self.header().namespace.is_null()
122    }
123
124    // === Internal ===
125
126    pub(crate) fn clone_impl(&self) -> Value {
127        #[cfg(feature = "alloc")]
128        {
129            let h = self.header();
130            Self::new(h.namespace.clone(), h.local_name.clone()).0
131        }
132        #[cfg(not(feature = "alloc"))]
133        {
134            panic!("cannot clone VQName without alloc feature")
135        }
136    }
137
138    pub(crate) fn drop_impl(&mut self) {
139        #[cfg(feature = "alloc")]
140        unsafe {
141            let ptr = self.0.heap_ptr_mut() as *mut QNameHeader;
142            // Drop the contained Values
143            core::ptr::drop_in_place(&mut (*ptr).namespace);
144            core::ptr::drop_in_place(&mut (*ptr).local_name);
145            Self::dealloc(ptr);
146        }
147    }
148}
149
150impl Clone for VQName {
151    fn clone(&self) -> Self {
152        VQName(self.clone_impl())
153    }
154}
155
156impl PartialEq for VQName {
157    fn eq(&self, other: &Self) -> bool {
158        let (h1, h2) = (self.header(), other.header());
159        h1.namespace == h2.namespace && h1.local_name == h2.local_name
160    }
161}
162
163impl Eq for VQName {}
164
165impl Hash for VQName {
166    fn hash<H: Hasher>(&self, state: &mut H) {
167        let h = self.header();
168        h.namespace.hash(state);
169        h.local_name.hash(state);
170    }
171}
172
173impl Debug for VQName {
174    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
175        let h = self.header();
176        if h.namespace.is_null() {
177            write!(f, "{:?}", h.local_name)
178        } else {
179            write!(f, "{{{:?}}}{:?}", h.namespace, h.local_name)
180        }
181    }
182}
183
184#[cfg(feature = "alloc")]
185impl From<VQName> for Value {
186    fn from(qname: VQName) -> Self {
187        qname.0
188    }
189}
190
191// ============================================================================
192// VUuid - UUID
193// ============================================================================
194
195/// Header for VUuid values.
196///
197/// Layout: [kind: u8][_pad: 7 bytes][uuid_bytes: 16 bytes]
198#[repr(C, align(8))]
199struct UuidHeader {
200    /// The OtherKind discriminant (always Uuid = 1)
201    kind: OtherKind,
202    /// Padding for alignment
203    _pad: [u8; 7],
204    /// The 128-bit UUID in big-endian byte order
205    bytes: [u8; 16],
206}
207
208/// A 128-bit universally unique identifier (UUID).
209///
210/// `VUuid` stores UUIDs in their native 128-bit form rather than as
211/// 36-character strings, preserving semantic identity while being more
212/// memory-efficient.
213#[repr(transparent)]
214pub struct VUuid(pub(crate) Value);
215
216impl VUuid {
217    const fn layout() -> Layout {
218        Layout::new::<UuidHeader>()
219    }
220
221    #[cfg(feature = "alloc")]
222    fn alloc() -> *mut UuidHeader {
223        unsafe { alloc(Self::layout()).cast::<UuidHeader>() }
224    }
225
226    #[cfg(feature = "alloc")]
227    fn dealloc(ptr: *mut UuidHeader) {
228        unsafe {
229            dealloc(ptr.cast::<u8>(), Self::layout());
230        }
231    }
232
233    fn header(&self) -> &UuidHeader {
234        unsafe { &*(self.0.heap_ptr() as *const UuidHeader) }
235    }
236
237    /// Creates a new UUID from 16 bytes (big-endian).
238    #[cfg(feature = "alloc")]
239    #[must_use]
240    pub fn new(bytes: [u8; 16]) -> Self {
241        unsafe {
242            let ptr = Self::alloc();
243            // Use ptr::write to avoid dropping uninitialized memory
244            core::ptr::write(&raw mut (*ptr).kind, OtherKind::Uuid);
245            core::ptr::write(&raw mut (*ptr)._pad, [0; 7]);
246            core::ptr::write(&raw mut (*ptr).bytes, bytes);
247            VUuid(Value::new_ptr(ptr.cast(), TypeTag::Other))
248        }
249    }
250
251    /// Creates a new UUID from two 64-bit integers (high and low parts).
252    #[cfg(feature = "alloc")]
253    #[must_use]
254    pub fn from_u64_pair(high: u64, low: u64) -> Self {
255        let mut bytes = [0u8; 16];
256        bytes[..8].copy_from_slice(&high.to_be_bytes());
257        bytes[8..].copy_from_slice(&low.to_be_bytes());
258        Self::new(bytes)
259    }
260
261    /// Creates a new UUID from a u128.
262    #[cfg(feature = "alloc")]
263    #[must_use]
264    pub fn from_u128(value: u128) -> Self {
265        Self::new(value.to_be_bytes())
266    }
267
268    /// Returns the UUID as 16 bytes (big-endian).
269    #[must_use]
270    pub fn as_bytes(&self) -> &[u8; 16] {
271        &self.header().bytes
272    }
273
274    /// Returns the UUID as a u128.
275    #[must_use]
276    pub fn as_u128(&self) -> u128 {
277        u128::from_be_bytes(self.header().bytes)
278    }
279
280    /// Returns the high 64 bits of the UUID.
281    #[must_use]
282    pub fn high(&self) -> u64 {
283        let bytes = &self.header().bytes;
284        u64::from_be_bytes([
285            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
286        ])
287    }
288
289    /// Returns the low 64 bits of the UUID.
290    #[must_use]
291    pub fn low(&self) -> u64 {
292        let bytes = &self.header().bytes;
293        u64::from_be_bytes([
294            bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
295        ])
296    }
297
298    // === Internal ===
299
300    pub(crate) fn clone_impl(&self) -> Value {
301        #[cfg(feature = "alloc")]
302        {
303            Self::new(self.header().bytes).0
304        }
305        #[cfg(not(feature = "alloc"))]
306        {
307            panic!("cannot clone VUuid without alloc feature")
308        }
309    }
310
311    pub(crate) fn drop_impl(&mut self) {
312        #[cfg(feature = "alloc")]
313        unsafe {
314            Self::dealloc(self.0.heap_ptr_mut().cast());
315        }
316    }
317}
318
319impl Clone for VUuid {
320    fn clone(&self) -> Self {
321        VUuid(self.clone_impl())
322    }
323}
324
325impl PartialEq for VUuid {
326    fn eq(&self, other: &Self) -> bool {
327        self.header().bytes == other.header().bytes
328    }
329}
330
331impl Eq for VUuid {}
332
333impl Hash for VUuid {
334    fn hash<H: Hasher>(&self, state: &mut H) {
335        self.header().bytes.hash(state);
336    }
337}
338
339impl Debug for VUuid {
340    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
341        let bytes = &self.header().bytes;
342        // Format as standard UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
343        write!(
344            f,
345            "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
346            bytes[0],
347            bytes[1],
348            bytes[2],
349            bytes[3],
350            bytes[4],
351            bytes[5],
352            bytes[6],
353            bytes[7],
354            bytes[8],
355            bytes[9],
356            bytes[10],
357            bytes[11],
358            bytes[12],
359            bytes[13],
360            bytes[14],
361            bytes[15]
362        )
363    }
364}
365
366#[cfg(feature = "alloc")]
367impl From<VUuid> for Value {
368    fn from(uuid: VUuid) -> Self {
369        uuid.0
370    }
371}
372
373#[cfg(feature = "alloc")]
374impl From<[u8; 16]> for VUuid {
375    fn from(bytes: [u8; 16]) -> Self {
376        Self::new(bytes)
377    }
378}
379
380#[cfg(feature = "alloc")]
381impl From<u128> for VUuid {
382    fn from(value: u128) -> Self {
383        Self::from_u128(value)
384    }
385}
386
387// ============================================================================
388// VChar - Unicode scalar value
389// ============================================================================
390
391/// Header for VChar values.
392///
393/// Layout: [kind: u8][_pad: 3 bytes][utf8_len: u8][utf8: 4 bytes][ch: u32]
394///
395/// We store both the raw `char` as a `u32` and its UTF-8 encoding (plus length)
396/// so we can offer a borrowed `&str` view without re-encoding.
397#[repr(C, align(8))]
398struct CharHeader {
399    /// The OtherKind discriminant (always Char = 2)
400    kind: OtherKind,
401    /// Padding for alignment
402    _pad: [u8; 3],
403    /// Number of valid bytes in `utf8`
404    utf8_len: u8,
405    /// UTF-8 encoding of the char (only the first `utf8_len` bytes are valid)
406    utf8: [u8; 4],
407    /// The raw char as a u32 scalar value
408    ch: u32,
409}
410
411/// A single Unicode scalar value (`char`).
412///
413/// `VChar` stores the character both as its raw `u32` scalar value and as its
414/// UTF-8 encoding, so it can be viewed either as a `char` or as a borrowed
415/// `&str` without re-encoding.
416#[repr(transparent)]
417pub struct VChar(pub(crate) Value);
418
419impl VChar {
420    const fn layout() -> Layout {
421        Layout::new::<CharHeader>()
422    }
423
424    #[cfg(feature = "alloc")]
425    fn alloc() -> *mut CharHeader {
426        unsafe { alloc(Self::layout()).cast::<CharHeader>() }
427    }
428
429    #[cfg(feature = "alloc")]
430    fn dealloc(ptr: *mut CharHeader) {
431        unsafe {
432            dealloc(ptr.cast::<u8>(), Self::layout());
433        }
434    }
435
436    fn header(&self) -> &CharHeader {
437        unsafe { &*(self.0.heap_ptr() as *const CharHeader) }
438    }
439
440    /// Creates a new `VChar` from a `char`.
441    #[cfg(feature = "alloc")]
442    #[must_use]
443    pub fn new(c: char) -> Self {
444        let mut utf8 = [0u8; 4];
445        let utf8_len = c.encode_utf8(&mut utf8).len() as u8;
446        unsafe {
447            let ptr = Self::alloc();
448            // Use ptr::write to avoid dropping uninitialized memory
449            core::ptr::write(&raw mut (*ptr).kind, OtherKind::Char);
450            core::ptr::write(&raw mut (*ptr)._pad, [0; 3]);
451            core::ptr::write(&raw mut (*ptr).utf8_len, utf8_len);
452            core::ptr::write(&raw mut (*ptr).utf8, utf8);
453            core::ptr::write(&raw mut (*ptr).ch, c as u32);
454            VChar(Value::new_ptr(ptr.cast(), TypeTag::Other))
455        }
456    }
457
458    /// Returns the stored character.
459    #[must_use]
460    pub fn value(&self) -> char {
461        // Safety: the stored u32 is always a valid char by construction.
462        unsafe { char::from_u32_unchecked(self.header().ch) }
463    }
464
465    /// Returns the UTF-8 encoding of the character as a borrowed `&str`.
466    #[must_use]
467    pub fn as_str(&self) -> &str {
468        let h = self.header();
469        let bytes = &h.utf8[..h.utf8_len as usize];
470        // Safety: the bytes are a valid UTF-8 encoding of a char by construction.
471        unsafe { core::str::from_utf8_unchecked(bytes) }
472    }
473
474    // === Internal ===
475
476    pub(crate) fn clone_impl(&self) -> Value {
477        #[cfg(feature = "alloc")]
478        {
479            Self::new(self.value()).0
480        }
481        #[cfg(not(feature = "alloc"))]
482        {
483            panic!("cannot clone VChar without alloc feature")
484        }
485    }
486
487    pub(crate) fn drop_impl(&mut self) {
488        #[cfg(feature = "alloc")]
489        unsafe {
490            Self::dealloc(self.0.heap_ptr_mut().cast());
491        }
492    }
493}
494
495impl Clone for VChar {
496    fn clone(&self) -> Self {
497        VChar(self.clone_impl())
498    }
499}
500
501impl PartialEq for VChar {
502    fn eq(&self, other: &Self) -> bool {
503        self.header().ch == other.header().ch
504    }
505}
506
507impl Eq for VChar {}
508
509impl Hash for VChar {
510    fn hash<H: Hasher>(&self, state: &mut H) {
511        self.value().hash(state);
512    }
513}
514
515impl Debug for VChar {
516    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
517        Debug::fmt(&self.value(), f)
518    }
519}
520
521#[cfg(feature = "alloc")]
522impl From<char> for VChar {
523    fn from(c: char) -> Self {
524        Self::new(c)
525    }
526}
527
528#[cfg(feature = "alloc")]
529impl From<char> for Value {
530    fn from(c: char) -> Self {
531        VChar::new(c).0
532    }
533}
534
535#[cfg(feature = "alloc")]
536impl From<VChar> for Value {
537    fn from(c: VChar) -> Self {
538        c.0
539    }
540}
541
542// ============================================================================
543// Helper to get OtherKind from a Value with tag 7
544// ============================================================================
545
546/// Returns the OtherKind for a Value that has TypeTag::Other.
547///
548/// # Safety
549/// The value must have TypeTag::Other (tag 7) and point to valid heap memory.
550pub(crate) unsafe fn get_other_kind(value: &Value) -> OtherKind {
551    // The first byte of any Other header is the OtherKind discriminant
552    let ptr = value.heap_ptr();
553    unsafe { *(ptr as *const OtherKind) }
554}
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559    use crate::VString;
560
561    #[test]
562    fn test_qname_with_namespace() {
563        let qname = VQName::new(VString::new("http://example.com"), VString::new("element"));
564        assert!(qname.has_namespace());
565        assert_eq!(
566            qname.namespace().unwrap().as_string().unwrap().as_str(),
567            "http://example.com"
568        );
569        assert_eq!(qname.local_name().as_string().unwrap().as_str(), "element");
570    }
571
572    #[test]
573    fn test_qname_local_only() {
574        let qname = VQName::new_local(VString::new("element"));
575        assert!(!qname.has_namespace());
576        assert!(qname.namespace().is_none());
577        assert_eq!(qname.local_name().as_string().unwrap().as_str(), "element");
578    }
579
580    #[test]
581    fn test_qname_clone() {
582        let qname = VQName::new(VString::new("ns"), VString::new("local"));
583        let cloned = qname.clone();
584        assert_eq!(qname, cloned);
585    }
586
587    #[test]
588    fn test_qname_debug() {
589        let qname = VQName::new(VString::new("ns"), VString::new("local"));
590        let debug = format!("{qname:?}");
591        assert!(debug.contains("ns"));
592        assert!(debug.contains("local"));
593    }
594
595    #[test]
596    fn test_uuid_new() {
597        let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
598        let uuid = VUuid::new(bytes);
599        assert_eq!(uuid.as_bytes(), &bytes);
600    }
601
602    #[test]
603    fn test_uuid_from_u128() {
604        let value: u128 = 0x0102030405060708090a0b0c0d0e0f10;
605        let uuid = VUuid::from_u128(value);
606        assert_eq!(uuid.as_u128(), value);
607    }
608
609    #[test]
610    fn test_uuid_high_low() {
611        let uuid = VUuid::from_u64_pair(0x0102030405060708, 0x090a0b0c0d0e0f10);
612        assert_eq!(uuid.high(), 0x0102030405060708);
613        assert_eq!(uuid.low(), 0x090a0b0c0d0e0f10);
614    }
615
616    #[test]
617    fn test_uuid_clone() {
618        let uuid = VUuid::from_u128(0x12345678_9abc_def0_1234_56789abcdef0);
619        let cloned = uuid.clone();
620        assert_eq!(uuid, cloned);
621    }
622
623    #[test]
624    fn test_uuid_debug_format() {
625        let uuid = VUuid::from_u128(0x12345678_9abc_def0_1234_56789abcdef0);
626        let debug = format!("{uuid:?}");
627        assert_eq!(debug, "12345678-9abc-def0-1234-56789abcdef0");
628    }
629
630    #[test]
631    fn test_char_value_and_str() {
632        let c = VChar::new('λ');
633        assert_eq!(c.value(), 'λ');
634        assert_eq!(c.as_str(), "λ");
635
636        let ascii = VChar::new('A');
637        assert_eq!(ascii.value(), 'A');
638        assert_eq!(ascii.as_str(), "A");
639
640        // 4-byte UTF-8 character
641        let emoji = VChar::new('\u{1F600}');
642        assert_eq!(emoji.value(), '\u{1F600}');
643        assert_eq!(emoji.as_str(), "\u{1F600}");
644    }
645
646    #[test]
647    fn test_char_clone_eq() {
648        let c = VChar::new('λ');
649        let cloned = c.clone();
650        assert_eq!(c, cloned);
651        assert_ne!(c, VChar::new('μ'));
652    }
653
654    #[test]
655    fn test_char_debug() {
656        let c = VChar::new('x');
657        assert_eq!(format!("{c:?}"), "'x'");
658    }
659}