1use std::{fmt, ops::Deref};
2
3use crate::{diagnostic::Diagnostic, doc_comment::DocComment};
4
5#[derive(Debug, Copy, Clone, Default)]
6pub struct Span<'a> {
7 source: &'a str,
8 pub start: usize,
9 pub len: usize,
10 pub file_id: usize,
11 pub source_offset: usize,
12}
13
14impl Span<'static> {
15 pub fn empty(file_id: usize) -> Self {
16 Span {
17 source: "",
18 file_id,
19 ..Default::default()
20 }
21 }
22}
23
24impl<'a> Span<'a> {
25 pub fn dummy(source: &'a str) -> Self {
26 Self {
27 source,
28 len: source.len(),
29 ..Default::default()
30 }
31 }
32
33 pub fn slice(&self, start: usize, len: usize) -> Self {
34 Self {
35 start: self.start + start,
36 len,
37 ..*self
38 }
39 }
40
41 pub fn as_str(&self) -> &'a str {
42 &self.source[self.start..self.start + self.len]
43 }
44
45 pub fn lines(self) -> impl Iterator<Item = Span<'a>> {
46 self.as_str().lines().map(move |line| self.from_slice(line))
47 }
48
49 pub fn splitn(self, n: usize, pat: &'static str) -> impl Iterator<Item = Span<'a>> {
50 self.as_str()
51 .splitn(n, pat)
52 .map(move |piece: &'a str| self.from_slice(piece))
53 }
54
55 pub fn trim(self) -> Self {
56 self.from_slice(self.as_str().trim())
57 }
58
59 pub fn strip_prefix(self, prefix: &str) -> Option<Self> {
60 Some(self.from_slice(self.as_str().strip_prefix(prefix)?))
61 }
62
63 pub fn diagnostic<S: Into<String>>(self, text: S) -> Diagnostic {
64 Diagnostic::from_span(text, self)
65 }
66
67 pub fn replace(&mut self, span: Self) {
68 *self = span }
70
71 fn from_slice(&self, text: &'a str) -> Self {
72 let start = text.as_ptr() as usize - self.source.as_ptr() as usize;
73
74 Span {
75 start,
76 len: text.len(),
77 ..*self
78 }
79 }
80}
81
82impl PartialEq for Span<'_> {
83 fn eq(&self, other: &Self) -> bool {
84 self.as_str() == other.as_str()
85 }
86}
87
88impl fmt::Display for Span<'_> {
89 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
90 formatter.write_str(self.as_str())
91 }
92}
93
94impl serde::Serialize for Span<'_> {
95 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
96 serializer.serialize_str(self.as_str())
97 }
98}
99
100impl Deref for Span<'_> {
101 type Target = str;
102 fn deref(&self) -> &str {
103 self.as_str()
104 }
105}
106
107impl<'a> From<&'a DocComment> for Span<'a> {
108 fn from(doc: &'a DocComment) -> Self {
109 Span {
110 source: &doc.comment,
111 len: doc.comment.len(),
112 file_id: doc.file_id,
113 source_offset: doc.start,
114 ..Default::default()
115 }
116 }
117}
118
119#[cfg(test)]
120mod test {
121 use super::*;
122
123 #[test]
124 fn correct_deref() {
125 let text = "abcdef";
126 let span = Span::dummy(text);
127
128 let deref_check: &str = &span;
129 assert_eq!(deref_check, text);
130
131 let slice = span.slice(1, 3);
132 let deref_slice_check: &str = &slice;
133 assert_eq!(deref_slice_check, "bcd");
134
135 let slice = slice.slice(1, 2);
136 let deref_slice_check: &str = &slice;
137 assert_eq!(deref_slice_check, "cd");
138 }
139
140 #[test]
141 fn lines() {
142 let text = "hello\nworld!\nipsum";
143 let span = Span::dummy(text);
144
145 let lines: Vec<_> = span.lines().map(|line| line.as_str()).collect();
146 assert_eq!(lines, &["hello", "world!", "ipsum"]);
147 }
148
149 #[test]
150 fn trim() {
151 let text = " hello ";
152 let span = Span::dummy(text);
153
154 assert_eq!(span.trim().as_str(), "hello");
155 }
156}