Skip to main content

ecma_syntax_cat/
span.rs

1//! Source-position newtypes and the [`Spanned`] wrapper.
2//!
3//! Every AST node in this crate is a [`Spanned`] value carrying both a kind
4//! enum and a [`Span`] denoting the source range the node was parsed from.
5//! Spans use 1-based line/column numbers and 0-based byte offsets, both
6//! recorded so downstream tools can pick whichever fits their layer.
7
8/// A source position: line, column, and byte offset.
9///
10/// Line and column are 1-based for human-readable diagnostics; offset is
11/// 0-based for slicing the source buffer.  All three are tracked because
12/// different layers want different forms: editors think in line/column,
13/// source-map generators think in offset.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct Position {
16    line: u32,
17    column: u32,
18    offset: u32,
19}
20
21impl Position {
22    /// Build a position.
23    #[must_use]
24    pub fn new(line: u32, column: u32, offset: u32) -> Self {
25        Self {
26            line,
27            column,
28            offset,
29        }
30    }
31
32    /// A synthetic position with zero offsets, useful for constructed nodes
33    /// that have no original source.
34    #[must_use]
35    pub fn synthetic() -> Self {
36        Self {
37            line: 0,
38            column: 0,
39            offset: 0,
40        }
41    }
42
43    /// The 1-based line number.
44    #[must_use]
45    pub fn line(&self) -> u32 {
46        self.line
47    }
48
49    /// The 1-based column number.
50    #[must_use]
51    pub fn column(&self) -> u32 {
52        self.column
53    }
54
55    /// The 0-based byte offset.
56    #[must_use]
57    pub fn offset(&self) -> u32 {
58        self.offset
59    }
60}
61
62impl std::fmt::Display for Position {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        write!(f, "{}:{}", self.line, self.column)
65    }
66}
67
68/// A half-open source range `[start, end)`.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub struct Span {
71    start: Position,
72    end: Position,
73}
74
75impl Span {
76    /// Build a span from start and end positions.
77    #[must_use]
78    pub fn new(start: Position, end: Position) -> Self {
79        Self { start, end }
80    }
81
82    /// A synthetic span (both endpoints at offset zero) for constructed
83    /// nodes that have no original source.
84    #[must_use]
85    pub fn synthetic() -> Self {
86        Self {
87            start: Position::synthetic(),
88            end: Position::synthetic(),
89        }
90    }
91
92    /// The start position.
93    #[must_use]
94    pub fn start(&self) -> Position {
95        self.start
96    }
97
98    /// The end position.
99    #[must_use]
100    pub fn end(&self) -> Position {
101        self.end
102    }
103
104    /// Whether this span contains `other` (inclusive of the same range).
105    #[must_use]
106    pub fn contains(&self, other: Span) -> bool {
107        self.start.offset() <= other.start.offset() && other.end.offset() <= self.end.offset()
108    }
109}
110
111impl std::fmt::Display for Span {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        write!(f, "{}..{}", self.start, self.end)
114    }
115}
116
117/// A value paired with its source span.
118///
119/// The wrapper centralises span tracking so each AST kind enum can focus on
120/// its semantic variants without having to thread a span field through
121/// every variant manually.
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct Spanned<T> {
124    value: T,
125    span: Span,
126}
127
128impl<T> Spanned<T> {
129    /// Build a spanned value.
130    pub fn new(value: T, span: Span) -> Self {
131        Self { value, span }
132    }
133
134    /// Borrow the inner value.
135    pub fn value(&self) -> &T {
136        &self.value
137    }
138
139    /// The span of this node.
140    #[must_use]
141    pub fn span(&self) -> Span {
142        self.span
143    }
144
145    /// Decompose into the inner value and its span.
146    pub fn into_parts(self) -> (T, Span) {
147        (self.value, self.span)
148    }
149
150    /// Map the inner value while preserving the span.
151    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Spanned<U> {
152        Spanned {
153            value: f(self.value),
154            span: self.span,
155        }
156    }
157}
158
159impl<T: std::fmt::Display> std::fmt::Display for Spanned<T> {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        self.value.fmt(f)
162    }
163}