Skip to main content

daml_syntax/
coordinate.rs

1//! Domain-specific source coordinates.
2//!
3//! Byte offsets, line numbers, and column positions use distinct newtypes so
4//! they cannot be passed to the wrong API by accident. Conversions to raw
5//! `usize` are explicit via inherent `get` methods or `usize::from`.
6
7use std::fmt;
8use std::num::NonZeroUsize;
9use text_size::TextSize;
10
11/// Error returned when constructing a 1-based coordinate from zero.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct InvalidOneBasedCoordinate {
14    value: usize,
15}
16
17impl InvalidOneBasedCoordinate {
18    /// Invalid raw coordinate value.
19    #[must_use]
20    pub const fn value(self) -> usize {
21        self.value
22    }
23}
24
25impl fmt::Display for InvalidOneBasedCoordinate {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        write!(f, "1-based coordinate value must be non-zero")
28    }
29}
30
31impl std::error::Error for InvalidOneBasedCoordinate {}
32
33macro_rules! define_zero_based_coordinate {
34    ($(#[$meta:meta])* $name:ident) => {
35        $(#[$meta])*
36        #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
37        pub struct $name(usize);
38
39        impl $name {
40            /// Creates a 0-based coordinate.
41            #[must_use]
42            pub const fn new(value: usize) -> Self {
43                Self(value)
44            }
45
46            /// Returns the underlying numeric value for this coordinate.
47            #[must_use]
48            pub const fn get(self) -> usize {
49                self.0
50            }
51        }
52
53        impl From<$name> for usize {
54            fn from(value: $name) -> Self {
55                value.get()
56            }
57        }
58
59        impl From<usize> for $name {
60            fn from(value: usize) -> Self {
61                Self::new(value)
62            }
63        }
64
65        impl fmt::Debug for $name {
66            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67                write!(f, "{}({})", stringify!($name), self.0)
68            }
69        }
70
71        impl fmt::Display for $name {
72            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73                self.0.fmt(f)
74            }
75        }
76    };
77}
78
79macro_rules! define_one_based_coordinate {
80    ($(#[$meta:meta])* $name:ident) => {
81        $(#[$meta])*
82        #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
83        pub struct $name(NonZeroUsize);
84
85        impl $name {
86            /// Creates a 1-based coordinate when `value` is non-zero.
87            #[must_use]
88            pub const fn try_new(value: usize) -> Option<Self> {
89                match NonZeroUsize::new(value) {
90                    Some(v) => Some(Self(v)),
91                    None => None,
92                }
93            }
94
95            /// Creates a 1-based coordinate.
96            ///
97            /// # Panics
98            ///
99            /// Panics when `value` is zero.
100            #[must_use]
101            pub const fn new(value: usize) -> Self {
102                match NonZeroUsize::new(value) {
103                    Some(v) => Self(v),
104                    None => panic!("1-based coordinates must be non-zero"),
105                }
106            }
107
108            /// Returns the underlying numeric value for this coordinate.
109            #[must_use]
110            pub const fn get(self) -> usize {
111                self.0.get()
112            }
113        }
114
115        impl From<$name> for usize {
116            fn from(value: $name) -> Self {
117                value.get()
118            }
119        }
120
121        impl TryFrom<usize> for $name {
122            type Error = InvalidOneBasedCoordinate;
123
124            fn try_from(value: usize) -> Result<Self, Self::Error> {
125                Self::try_new(value).ok_or(InvalidOneBasedCoordinate { value })
126            }
127        }
128
129        impl fmt::Debug for $name {
130            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131                write!(f, "{}({})", stringify!($name), self.0)
132            }
133        }
134
135        impl fmt::Display for $name {
136            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137                self.0.fmt(f)
138            }
139        }
140    };
141}
142
143define_one_based_coordinate!(
144    /// 1-based line number in a source file.
145    ///
146    /// Zero is invalid; use [`LineNumber::try_new`] to reject it explicitly.
147    LineNumber
148);
149define_one_based_coordinate!(
150    /// 1-based byte column offset from the start of a line.
151    ///
152    /// Zero is invalid; use [`ByteColumn::try_new`] to reject it explicitly.
153    ByteColumn
154);
155define_one_based_coordinate!(
156    /// 1-based Unicode scalar column offset from the start of a line.
157    ///
158    /// Zero is invalid; use [`CharColumn::try_new`] to reject it explicitly.
159    CharColumn
160);
161define_zero_based_coordinate!(
162    /// 0-based UTF-8 byte offset into source text.
163    ///
164    /// Prefer [`TextSize`] for [`text_size::TextRange`] construction; this
165    /// newtype marks raw parser byte offsets at crate boundaries.
166    ByteOffset
167);
168define_zero_based_coordinate!(
169    /// 0-based UTF-16 code-unit offset into source text.
170    Utf16Offset
171);
172
173/// 0-based half-open range in UTF-16 code units.
174///
175/// Use this when interoperating with JavaScript-style string ranges. The named
176/// endpoints avoid confusing UTF-16 offsets with byte ranges or line/column
177/// pairs.
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
179pub struct Utf16Range {
180    start: Utf16Offset,
181    end: Utf16Offset,
182}
183
184impl Utf16Range {
185    /// Creates a UTF-16 range from inclusive start and exclusive end offsets.
186    #[must_use]
187    pub const fn new(start: Utf16Offset, end: Utf16Offset) -> Self {
188        Self { start, end }
189    }
190
191    /// Inclusive 0-based start offset in UTF-16 code units.
192    #[must_use]
193    pub const fn start(self) -> Utf16Offset {
194        self.start
195    }
196
197    /// Exclusive 0-based end offset in UTF-16 code units.
198    #[must_use]
199    pub const fn end(self) -> Utf16Offset {
200        self.end
201    }
202}
203
204impl From<TextSize> for ByteOffset {
205    fn from(offset: TextSize) -> Self {
206        Self::new(usize::from(offset))
207    }
208}
209
210impl TryFrom<ByteOffset> for TextSize {
211    type Error = std::num::TryFromIntError;
212
213    fn try_from(offset: ByteOffset) -> Result<Self, Self::Error> {
214        Self::try_from(offset.get())
215    }
216}
217
218/// 1-based line and byte-column position returned by [`super::LineIndex::line_col`].
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub struct ByteLineCol {
221    /// 1-based line number.
222    pub line: LineNumber,
223    /// 1-based byte column on that line.
224    pub column: ByteColumn,
225}
226
227/// 1-based line and character-column position returned by
228/// [`super::LineIndex::char_line_col`].
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub struct CharLineCol {
231    /// 1-based line number.
232    pub line: LineNumber,
233    /// 1-based Unicode scalar column on that line.
234    pub column: CharColumn,
235}