facet_deserialize/
span.rs

1use alloc::vec::Vec;
2use core::fmt;
3use core::marker::PhantomData;
4
5/// A Cooked variant of a Span (byte indexed)
6#[derive(Debug, PartialEq)]
7pub enum Cooked {}
8
9/// A Raw variant of a Span (format-specific index)
10#[derive(Debug, PartialEq)]
11pub enum Raw {}
12
13/// Position in the input (byte index)
14pub type Pos = usize;
15
16/// A span in the input, with a start position and length
17#[derive(Debug, PartialEq, Eq)]
18pub struct Span<C = Cooked> {
19    /// Starting position of the span in bytes
20    pub start: Pos,
21    /// Length of the span in bytes
22    pub len: usize,
23    /// Hold on to C
24    _p: PhantomData<C>,
25}
26
27/// Trait for types that can be annotated with a Span.
28pub trait Spannable<C = Cooked>: Sized {
29    /// Annotate this value with a span, wrapping it in `Spanned<Self, C>`
30    fn with_span(self, span: Span<C>) -> Spanned<Self, C>;
31}
32
33impl<T, C> Spannable<C> for T {
34    fn with_span(self, span: Span<C>) -> Spanned<Self, C> {
35        Spanned { node: self, span }
36    }
37}
38
39impl<C> Span<C> {
40    /// Creates a new span with the given start position and length
41    pub fn new(start: Pos, len: usize) -> Self {
42        Span {
43            start,
44            len,
45            _p: PhantomData,
46        }
47    }
48    /// Start position of the span
49    pub fn start(&self) -> Pos {
50        self.start
51    }
52    /// Length of the span
53    pub fn len(&self) -> usize {
54        self.len
55    }
56    /// Returns `true` if this span has zero length
57    pub fn is_empty(&self) -> bool {
58        self.len == 0
59    }
60    /// End position (start + length)
61    pub fn end(&self) -> Pos {
62        self.start + self.len
63    }
64}
65
66impl<C> Default for Span<C> {
67    fn default() -> Self {
68        Span {
69            start: 0,
70            len: 0,
71            _p: PhantomData,
72        }
73    }
74}
75
76/// A value of type `T` annotated with its `Span`
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct Spanned<T, C = Cooked> {
79    /// The actual data/value being wrapped
80    pub node: T,
81    /// The span information indicating the position and length in the source
82    pub span: Span<C>,
83}
84
85impl<T: fmt::Display, C> fmt::Display for Spanned<T, C> {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        write!(
88            f,
89            "{} at {}-{}",
90            self.node,
91            self.span.start(),
92            self.span.end()
93        )
94    }
95}
96
97// Copy + Clone not auto-derived for PhantomData
98// https://stackoverflow.com/a/31371094/2668831
99
100impl<C> Clone for Span<C> {
101    fn clone(&self) -> Self {
102        *self
103    }
104}
105
106impl<C> Copy for Span<C> {}
107
108/// A Subspan variant of a Span
109#[derive(Clone, Debug, PartialEq)]
110pub struct Subspan {
111    /// Offset from parent span's start
112    pub offset: usize,
113    /// Length of the subspan
114    pub len: usize,
115    /// Optional metadata (like delimiter information)
116    pub meta: Option<SubspanMeta>,
117}
118
119/// Metadata about a subspan, providing context for how the subspan relates
120/// to the parent span or other subspans.
121#[derive(Debug, Copy, Clone, PartialEq)]
122pub enum SubspanMeta {
123    /// Indicates the subspan is part of a delimited sequence,
124    /// storing the delimiter character (e.g., ',' in "1,2,3")
125    Delimiter(char),
126
127    /// Indicates the subspan represents one side of a key-value pair
128    /// (e.g., in "--key=value" or "-k=val")
129    KeyValue,
130    // Other metadata cases as needed...
131}
132
133/// Container for subspans based on span type
134pub struct Substack<C> {
135    spans: Option<Vec<Subspan>>,
136    _marker: PhantomData<C>,
137}
138
139impl<C> Substack<C> {
140    /// Initialise subspan stack as None
141    pub fn new() -> Self {
142        Substack {
143            spans: None,
144            _marker: PhantomData,
145        }
146    }
147
148    /// Get all stored spans
149    pub fn get(&self) -> &[Subspan] {
150        match &self.spans {
151            Some(spans) => spans,
152            None => &[], // Return empty slice if no spans are stored
153        }
154    }
155
156    /// Pop the most recently added subspan
157    pub fn pop(&mut self) -> Option<Subspan> {
158        if let Some(spans) = &mut self.spans {
159            spans.pop()
160        } else {
161            None
162        }
163    }
164
165    /// Clear all subspans
166    pub fn clear(&mut self) {
167        if let Some(spans) = &mut self.spans {
168            spans.clear();
169        }
170    }
171}
172
173impl<C> Default for Substack<C> {
174    fn default() -> Self {
175        Self::new()
176    }
177}
178
179impl<C> From<Vec<Subspan>> for Substack<C> {
180    fn from(subspans: Vec<Subspan>) -> Self {
181        Substack {
182            spans: Some(subspans),
183            _marker: PhantomData,
184        }
185    }
186}
187
188impl Substack<Raw> {
189    /// Add a subspan for Raw spans
190    pub fn add(&mut self, offset: usize, len: usize, meta: Option<SubspanMeta>) {
191        if self.spans.is_none() {
192            self.spans = Some(Vec::new());
193        }
194
195        if let Some(spans) = &mut self.spans {
196            spans.push(Subspan { offset, len, meta });
197        }
198    }
199
200    /// Add a simple subspan with just offset and length
201    pub fn add_simple(&mut self, offset: usize, len: usize) {
202        self.add(offset, len, None);
203    }
204
205    /// Add a delimiter subspan
206    pub fn add_delimiter(&mut self, offset: usize, len: usize, delimiter: char) {
207        self.add(offset, len, Some(SubspanMeta::Delimiter(delimiter)));
208    }
209
210    /// Add a key-value subspan
211    pub fn add_key_value(&mut self, offset: usize, len: usize) {
212        self.add(offset, len, Some(SubspanMeta::KeyValue));
213    }
214}
215
216impl Substack<Cooked> {
217    /// Add a span for Cooked spans (does nothing)
218    pub fn add(&mut self, _offset: usize, _len: usize, _meta: Option<SubspanMeta>) {}
219
220    /// Add a simple subspan (does nothing for Cooked)
221    pub fn add_simple(&mut self, _offset: usize, _len: usize) {}
222
223    /// Add a delimiter subspan (does nothing for Cooked)
224    pub fn add_delimiter(&mut self, _offset: usize, _len: usize, _delimiter: char) {}
225
226    /// Add a key-value subspan (does nothing for Cooked)
227    pub fn add_key_value(&mut self, _offset: usize, _len: usize) {}
228}
229
230/// This trait allows the compiler to optimize away `Substack`-related code
231/// for formats with span types that don't use subspans, making it zero-cost.
232pub trait SubstackBehavior {
233    /// Whether to use subspans in the `deserialize_wip` instruction stack loop.
234    const USES_SUBSTACK: bool;
235}
236
237impl SubstackBehavior for Raw {
238    const USES_SUBSTACK: bool = true;
239}
240
241impl SubstackBehavior for Cooked {
242    const USES_SUBSTACK: bool = false;
243}