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