1use std::{cmp::Ordering, path::PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Default, Serialize, Deserialize, Clone)]
6pub struct ShaderPosition {
7 pub line: u32,
8 pub pos: u32,
9}
10impl Eq for ShaderPosition {}
11
12impl Ord for ShaderPosition {
13 fn cmp(&self, other: &Self) -> Ordering {
14 (&self.line, &self.pos).cmp(&(&other.line, &other.pos))
15 }
16}
17
18impl PartialOrd for ShaderPosition {
19 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
20 Some(self.cmp(other))
21 }
22}
23
24impl PartialEq for ShaderPosition {
25 fn eq(&self, other: &Self) -> bool {
26 (&self.line, &self.pos) == (&other.line, &other.pos)
27 }
28}
29
30impl ShaderPosition {
31 pub fn new(line: u32, pos: u32) -> Self {
32 Self { line, pos }
33 }
34 pub fn zero() -> Self {
35 Self { line: 0, pos: 0 }
36 }
37 pub fn into_file(self, file_path: PathBuf) -> ShaderFilePosition {
38 ShaderFilePosition::from(file_path, self)
39 }
40 pub fn clone_into_file(&self, file_path: PathBuf) -> ShaderFilePosition {
41 ShaderFilePosition::from(file_path, self.clone())
42 }
43 pub fn from_byte_offset(content: &str, byte_offset: usize) -> std::io::Result<ShaderPosition> {
44 if byte_offset == 0 {
46 Ok(ShaderPosition::zero())
47 } else if content.len() == 0 {
48 Err(std::io::Error::new(
49 std::io::ErrorKind::InvalidInput,
50 "Content is empty.",
51 ))
52 } else if byte_offset > content.len() {
53 Err(std::io::Error::new(
54 std::io::ErrorKind::InvalidInput,
55 "byte_offset is out of bounds.",
56 ))
57 } else {
58 let line = content[..byte_offset].split('\n').count() - 1;
62 let line_start = content[..byte_offset]
63 .split('\n')
64 .rev()
65 .next()
66 .expect("No last line available.");
67 let pos_in_byte =
68 content[byte_offset..].as_ptr() as usize - line_start.as_ptr() as usize;
69 if line_start.is_char_boundary(pos_in_byte) {
70 Ok(ShaderPosition::new(
71 line as u32,
72 line_start[..pos_in_byte].chars().count() as u32,
73 ))
74 } else {
75 Err(std::io::Error::new(
76 std::io::ErrorKind::InvalidData,
77 "Pos in line is not at UTF8 char boundary.",
78 ))
79 }
80 }
81 }
82 pub fn to_byte_offset(&self, content: &str) -> std::io::Result<usize> {
83 match content.lines().nth(self.line as usize) {
85 Some(line) => {
86 let line_byte_offset = line.as_ptr() as usize - content.as_ptr() as usize;
88 assert!(
89 content.is_char_boundary(line_byte_offset),
90 "Start of line is not char boundary."
91 );
92 match content[line_byte_offset..]
94 .char_indices()
95 .nth(self.pos as usize)
96 {
97 Some((byte_offset, _)) => {
98 let global_offset = line_byte_offset + byte_offset;
99 if content.len() <= global_offset {
100 Err(std::io::Error::new(
101 std::io::ErrorKind::InvalidData,
102 "Byte offset is not in content range.",
103 ))
104 } else if !content.is_char_boundary(global_offset) {
105 Err(std::io::Error::new(
106 std::io::ErrorKind::InvalidData,
107 "Position is not at UTF8 char boundary.",
108 ))
109 } else {
110 Ok(global_offset)
111 }
112 }
113 None => {
114 if self.pos as usize == line.chars().count() {
115 assert!(content.is_char_boundary(line_byte_offset + line.len()));
116 Ok(line_byte_offset + line.len())
117 } else {
118 Err(std::io::Error::new(
119 std::io::ErrorKind::InvalidInput,
120 format!("Position is not in range of line"),
121 ))
122 }
123 }
124 }
125 }
126 None => Ok(content.len()), }
129 }
130}
131
132#[derive(Debug, Default, Serialize, Deserialize, Clone)]
133pub struct ShaderFilePosition {
134 pub file_path: PathBuf,
135 pub position: ShaderPosition,
136}
137impl Eq for ShaderFilePosition {}
138
139impl Ord for ShaderFilePosition {
140 fn cmp(&self, other: &Self) -> Ordering {
141 assert!(
142 self.file_path == other.file_path,
143 "Cannot compare file from different path"
144 );
145 (&self.file_path, &self.position.line, &self.position.pos).cmp(&(
146 &other.file_path,
147 &other.position.line,
148 &other.position.pos,
149 ))
150 }
151}
152
153impl PartialOrd for ShaderFilePosition {
154 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
155 Some(self.cmp(other))
156 }
157}
158
159impl PartialEq for ShaderFilePosition {
160 fn eq(&self, other: &Self) -> bool {
161 (&self.file_path, &self.position.line, &self.position.pos)
162 == (&other.file_path, &other.position.line, &other.position.pos)
163 }
164}
165
166impl ShaderFilePosition {
167 pub fn from(file_path: PathBuf, position: ShaderPosition) -> Self {
168 Self {
169 file_path,
170 position,
171 }
172 }
173 pub fn new(file_path: PathBuf, line: u32, pos: u32) -> Self {
174 Self {
175 file_path,
176 position: ShaderPosition::new(line, pos),
177 }
178 }
179 pub fn zero(file_path: PathBuf) -> Self {
180 Self {
181 file_path,
182 position: ShaderPosition::zero(),
183 }
184 }
185 pub fn pos(&self) -> u32 {
186 self.position.pos
187 }
188 pub fn line(&self) -> u32 {
189 self.position.line
190 }
191}
192
193#[derive(Debug, Default, Clone, PartialEq, Eq)]
194pub struct ShaderRange {
195 pub start: ShaderPosition,
196 pub end: ShaderPosition,
197}
198
199impl ShaderRange {
200 pub fn new(start: ShaderPosition, end: ShaderPosition) -> Self {
201 Self { start, end }
202 }
203 pub fn zero() -> Self {
204 Self::new(ShaderPosition::zero(), ShaderPosition::zero())
205 }
206 pub fn into_file(self, file_path: PathBuf) -> ShaderFileRange {
207 ShaderFileRange::from(file_path, self)
208 }
209 pub fn clone_into_file(&self, file_path: PathBuf) -> ShaderFileRange {
210 ShaderFileRange::from(file_path, self.clone())
211 }
212 pub fn whole(content: &str) -> Self {
213 let line_count = content.lines().count() as u32;
214 let char_count = match content.lines().last() {
215 Some(last_line) => (last_line.char_indices().count()) as u32, None => (content.char_indices().count()) as u32, };
218 Self {
219 start: ShaderPosition::new(0, 0),
220 end: ShaderPosition::new(line_count, char_count),
221 }
222 }
223 pub fn contain_bounds(&self, range: &ShaderRange) -> bool {
224 if range.start.line > self.start.line && range.end.line < self.end.line {
225 true
226 } else if range.start.line == self.start.line && range.end.line == self.end.line {
227 range.start.pos >= self.start.pos && range.end.pos <= self.end.pos
228 } else if range.start.line == self.start.line && range.end.line < self.end.line {
229 range.start.pos >= self.start.pos
230 } else if range.end.line == self.end.line && range.start.line > self.start.line {
231 range.end.pos <= self.end.pos
232 } else {
233 false
234 }
235 }
236 pub fn contain(&self, position: &ShaderPosition) -> bool {
237 if position.line > self.start.line && position.line < self.end.line {
239 true
240 } else if position.line == self.start.line && position.line == self.end.line {
241 position.pos >= self.start.pos && position.pos <= self.end.pos
242 } else if position.line == self.start.line && position.line < self.end.line {
243 position.pos >= self.start.pos
244 } else if position.line == self.end.line && position.line > self.start.line {
245 position.pos <= self.end.pos
246 } else {
247 false
248 }
249 }
250 pub fn join(mut lhs: ShaderRange, rhs: ShaderRange) -> ShaderRange {
251 lhs.start.line = std::cmp::min(lhs.start.line, rhs.start.line);
252 lhs.start.pos = std::cmp::min(lhs.start.pos, rhs.start.pos);
253 lhs.end.line = std::cmp::max(lhs.end.line, rhs.end.line);
254 lhs.end.pos = std::cmp::max(lhs.end.pos, rhs.end.pos);
255 lhs
256 }
257}
258
259#[derive(Debug, Default, Clone, PartialEq, Eq)]
260pub struct ShaderFileRange {
261 pub file_path: PathBuf,
262 pub range: ShaderRange,
263}
264
265impl ShaderFileRange {
266 pub fn from(file_path: PathBuf, range: ShaderRange) -> Self {
267 Self { file_path, range }
268 }
269 pub fn new(file_path: PathBuf, start: ShaderPosition, end: ShaderPosition) -> Self {
270 Self {
271 file_path,
272 range: ShaderRange::new(start, end),
273 }
274 }
275 pub fn zero(file_path: PathBuf) -> Self {
276 Self::new(file_path, ShaderPosition::zero(), ShaderPosition::zero())
277 }
278 pub fn whole(file_path: PathBuf, content: &str) -> Self {
279 Self::from(file_path, ShaderRange::whole(content))
280 }
281 pub fn start(&self) -> &ShaderPosition {
282 &self.range.start
283 }
284 pub fn end(&self) -> &ShaderPosition {
285 &self.range.end
286 }
287 pub fn start_as_file_position(&self) -> ShaderFilePosition {
288 ShaderFilePosition::from(self.file_path.clone(), self.range.start.clone())
289 }
290 pub fn end_as_file_position(&self) -> ShaderFilePosition {
291 ShaderFilePosition::from(self.file_path.clone(), self.range.end.clone())
292 }
293 pub fn contain_bounds(&self, range: &ShaderFileRange) -> bool {
294 if self.file_path.as_os_str() == range.file_path.as_os_str() {
295 debug_assert!(
296 range.file_path == self.file_path,
297 "Raw string identical but not components"
298 );
299 self.range.contain_bounds(&range.range)
300 } else {
301 debug_assert!(
302 range.file_path != self.file_path,
303 "Raw string different but not components"
304 );
305 false
306 }
307 }
308 pub fn contain(&self, position: &ShaderFilePosition) -> bool {
309 if position.file_path.as_os_str() == self.file_path.as_os_str() {
311 debug_assert!(
312 position.file_path == self.file_path,
313 "Raw string identical but not components"
314 );
315 self.range.contain(&position.position)
316 } else {
317 debug_assert!(
318 position.file_path != self.file_path,
319 "Raw string different but not components"
320 );
321 false
322 }
323 }
324 pub fn join(mut lhs: ShaderFileRange, rhs: ShaderFileRange) -> ShaderFileRange {
325 lhs.range = ShaderRange::join(lhs.range, rhs.range);
326 lhs
327 }
328}