libgraphql_parser/byte_span.rs
1use crate::{SourceMap, SourceSpan};
2
3/// A compact source span representing a half-open byte range `[start, end)`.
4///
5/// This is the primary span type stored on all tokens, AST nodes, and parse
6/// errors. It is 8 bytes, `Copy`, and `#[repr(C)]` for predictable layout.
7///
8/// `ByteSpan` does **not** carry line/column/file information — those are
9/// resolved on demand via [`SourceMap::resolve_span()`](crate::SourceMap::resolve_span).
10///
11/// # Indexing Convention
12///
13/// Both `start` and `end` are 0-based byte offsets from the beginning of the
14/// source text. The range is half-open: `start` is the byte offset of the
15/// first character, and `end` is the byte offset immediately after the last
16/// character.
17#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
18#[repr(C)]
19pub struct ByteSpan {
20 /// Byte offset of the first character (inclusive).
21 pub start: u32,
22
23 /// Byte offset immediately after the last character (exclusive).
24 pub end: u32,
25}
26
27impl ByteSpan {
28 /// Creates a new `ByteSpan` from start (inclusive) and end (exclusive)
29 /// byte offsets.
30 #[inline]
31 pub fn new(start: u32, end: u32) -> Self {
32 debug_assert!(
33 start <= end,
34 "ByteSpan::new called with start ({start}) > end ({end})",
35 );
36 Self { start, end }
37 }
38
39 /// Returns the length of this span in bytes.
40 #[inline]
41 pub fn len(&self) -> u32 {
42 self.end - self.start
43 }
44
45 /// Returns `true` if the span is empty (zero length).
46 #[inline]
47 pub fn is_empty(&self) -> bool {
48 self.start == self.end
49 }
50
51 /// Creates an empty span at the given byte offset.
52 ///
53 /// Useful for representing zero-width positions (e.g., EOF).
54 #[inline]
55 pub fn empty_at(offset: u32) -> Self {
56 Self {
57 start: offset,
58 end: offset,
59 }
60 }
61
62 /// Creates a span that covers both `self` and `other`.
63 ///
64 /// The resulting span starts at the minimum start and ends at the maximum
65 /// end of the two spans.
66 #[inline]
67 pub fn merge(self, other: ByteSpan) -> ByteSpan {
68 ByteSpan {
69 start: self.start.min(other.start),
70 end: self.end.max(other.end),
71 }
72 }
73
74 /// Resolves this byte span to a [`SourceSpan`] with
75 /// line/column positions using the given [`SourceMap`].
76 ///
77 /// Returns [`None`] if the byte offsets cannot be resolved.
78 /// Convenience wrapper for
79 /// [`SourceMap::resolve_span()`].
80 #[inline]
81 pub fn resolve(&self, source_map: &SourceMap) -> Option<SourceSpan> {
82 source_map.resolve_span(*self)
83 }
84}