span/lib.rs
1//! Utilities to represent spans inside a file
2
3use std::{fmt, fmt::Debug, ops::Deref, str};
4
5pub mod source;
6#[doc(hidden)]
7pub use source::SourceMap;
8
9/// Represents a span in the [`SourceMap`], bounded by an offset and a len
10///
11/// # About offset
12/// The offset of this Span is an absolute offset inside the [`SourceMap`]
13/// It doesn't represent the offset relative to the file it was created from.
14/// This is done to avoid havind an additional field to identify the source file.
15///
16/// To get the base offset of a [`Span`] you can use the
17/// [`SourceMap::get_base_offset_of_span`] function
18#[derive(Clone, Copy, Default, PartialEq)]
19pub struct Span {
20 /// Offset of the span
21 pub offset: usize,
22 /// Length of the span
23 pub len: usize
24}
25
26impl Debug for Span {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 let Span { offset, len } = self;
29 write!(f, "Span {{ {offset}, {len} }}")
30 }
31}
32
33/// Represents a [`Span`] in a file, bounded by
34/// it's start line and col, plus it's end line and col
35#[derive(Debug, Clone, Copy, Default, PartialEq)]
36pub struct FilePosition {
37 pub start_line: usize,
38 pub start_col: usize,
39 pub end_line: usize,
40 pub end_col: usize,
41}
42
43impl fmt::Display for FilePosition {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 let FilePosition {
46 start_line,
47 start_col,
48 ..
49 } = self;
50 write!(f, "[{start_line}:{start_col}]")
51 }
52}
53
54impl Span {
55
56 pub const fn dummy() -> Span { Span { offset: 0, len: 0 } }
57
58 /// Joins two spans together.
59 /// Returns the smallest Span that covers both.
60 #[must_use]
61 pub const fn join(&self, other: &Span) -> Span {
62 let (left, right) = if self.offset < other.offset {
63 (self, other)
64 } else {
65 (other, self)
66 };
67 Span {
68 offset: left.offset,
69 len: right.end_offset() - left.offset,
70 }
71 }
72 /// Slices the given string with this span
73 ///
74 /// `base_offset` is the base offset of the corresponding
75 /// [`SourceFile`](source::SourceFile) for this `Span`
76 ///
77 /// You can also use the [`SourceMap::slice`] function
78 ///
79 /// See: [about offset](Self#about-offset)
80 ///
81 /// # Examples
82 ///
83 /// Simple example with a single source file
84 /// ```
85 /// use span::Span;
86 ///
87 /// let src = "abcdefg";
88 /// let span = Span {
89 /// offset: 1,
90 /// len: 5,
91 /// };
92 /// let slice = span.slice(0, src);
93 /// assert_eq!("bcdef", slice);
94 /// ```
95 ///
96 /// Example with multiple source files
97 /// ```
98 /// use span::{Span, source::SourceMap};
99 ///
100 /// let src1 = "abcdefghi";
101 /// let src2 = "abcdefhij";
102 ///
103 /// let mut source = SourceMap::default();
104 /// let offset1 = source.add_file_annon(src1.into()).offset();
105 /// let offset2 = source.add_file_annon(src2.into()).offset();
106 ///
107 /// let span1 = Span {
108 /// offset: offset1 + 3,
109 /// len: 4,
110 /// };
111 ///
112 /// let span2 = Span {
113 /// offset: offset2 + 4,
114 /// len: 2,
115 /// };
116 ///
117 /// let slice1 = span1.slice(offset1, src1);
118 /// let slice2 = span2.slice(offset2, src1);
119 ///
120 /// assert_eq!(slice1, "defg");
121 /// assert_eq!(slice2, "ef");
122 ///
123 /// ```
124 #[must_use]
125 #[inline]
126 pub fn slice<'a>(&self, base_offset: usize, src: &'a str) -> &'a str {
127 let start = self.offset - base_offset;
128 let end = start + self.len;
129 &src[start..end]
130 }
131 /// Gets the [file position](FilePosition) of this span in the given string slice
132 ///
133 /// `base_offset` is the base offset of the corresponding
134 /// [`SourceFile`](source::SourceFile) for this `Span`
135 ///
136 /// You can also use the [`SourceMap::file_position`] function
137 ///
138 /// See: [about offset](Self#about-offset)
139 #[must_use]
140 pub fn file_position(&self, base_offset: usize, src: &str) -> FilePosition {
141 let mut fpos = FilePosition {
142 start_line: 1,
143 start_col: 0,
144 end_line: 0,
145 end_col: 0,
146 };
147
148 let start = self.offset - base_offset;
149
150 for c in src[..start].chars() {
151 if c == '\n' {
152 fpos.start_col = 0;
153 fpos.start_line += 1;
154 }
155 fpos.start_col += 1;
156 }
157
158 fpos.end_line = fpos.start_line;
159 fpos.end_col = fpos.start_col;
160
161 let end = start + self.len;
162 for c in src[start..end].chars() {
163 if c == '\n' {
164 fpos.end_col = 0;
165 fpos.end_line += 1;
166 }
167 fpos.end_col += 1;
168 }
169
170 fpos
171 }
172 /// Returns the end offset of the span. This is, the
173 /// offset of the span plus it's length
174 #[must_use]
175 #[inline]
176 pub const fn end_offset(&self) -> usize { self.offset + self.len }
177}
178
179impl fmt::Display for Span {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 write!(f, "[{}:{}]", self.offset, self.offset + self.len)
182 }
183}
184
185#[derive(Debug)]
186pub struct Spanned<T> {
187 pub val: T,
188 pub span: Span,
189}
190
191impl<T> Deref for Spanned<T> {
192 type Target = T;
193
194 fn deref(&self) -> &Self::Target { &self.val }
195}
196
197impl<T: Clone> Clone for Spanned<T> {
198 fn clone(&self) -> Self {
199 Self {
200 val: self.val.clone(),
201 span: self.span,
202 }
203 }
204}
205
206impl<T: PartialEq> PartialEq for Spanned<T> {
207 fn eq(&self, other: &Self) -> bool { self.val == other.val }
208}