surrealdb_core/syn/error/
location.rs

1use crate::syn::token::Span;
2use std::ops::Range;
3
4/// A human readable location inside a string.
5///
6/// Locations are 1 indexed, the first character on the first line being on line 1 column 1.
7#[derive(Clone, Copy, Debug)]
8#[non_exhaustive]
9pub struct Location {
10	pub line: usize,
11	/// In chars.
12	pub column: usize,
13}
14
15/// Safety: b must be a substring of a.
16unsafe fn str_offset(a: &str, b: &str) -> usize {
17	b.as_ptr().offset_from(a.as_ptr()) as usize
18}
19
20impl Location {
21	fn range_of_source_end(source: &str) -> Range<Self> {
22		let (line, column) = source
23			.lines()
24			.enumerate()
25			.last()
26			.map(|(idx, line)| {
27				let idx = idx + 1;
28				let line_idx = line.chars().count().max(1);
29				(idx, line_idx)
30			})
31			.unwrap_or((1, 1));
32
33		Self {
34			line,
35			column,
36		}..Self {
37			line,
38			column: column + 1,
39		}
40	}
41	pub fn range_of_span(source: &str, span: Span) -> Range<Self> {
42		if source.len() <= span.offset as usize {
43			return Self::range_of_source_end(source);
44		}
45
46		let mut prev_line = "";
47		let mut lines = source.lines().enumerate().peekable();
48		// Bytes of input prior to line being iteratated.
49		let start_offset = span.offset as usize;
50		let start = loop {
51			let Some((line_idx, line)) = lines.peek().copied() else {
52				// Couldn't find the line, give up and return the last
53				return Self::range_of_source_end(source);
54			};
55			// Safety: line originates from source so it is a substring so calling str_offset is
56			// valid.
57			let line_offset = unsafe { str_offset(source, line) };
58
59			if start_offset < line_offset {
60				// Span is inside the previous line terminator, point to the end of the line.
61				let len = prev_line.chars().count();
62				break Self {
63					line: line_idx,
64					column: len + 1,
65				};
66			}
67
68			if (line_offset..(line_offset + line.len())).contains(&start_offset) {
69				let column_offset = start_offset - line_offset;
70				let column = line
71					.char_indices()
72					.enumerate()
73					.find(|(_, (char_idx, _))| *char_idx >= column_offset)
74					.map(|(l, _)| l)
75					.unwrap_or_else(|| {
76						// give up, just point to the end.
77						line.chars().count()
78					});
79				break Self {
80					line: line_idx + 1,
81					column: column + 1,
82				};
83			}
84
85			lines.next();
86			prev_line = line;
87		};
88
89		let end_offset = span.offset as usize + span.len as usize;
90		let end = loop {
91			let Some((line_idx, line)) = lines.peek().copied() else {
92				// Couldn't find the line, give up and return the last
93				break Self::range_of_source_end(source).end;
94			};
95			// Safety: line originates from source so it is a substring so calling str_offset is
96			// valid.
97			let line_offset = unsafe { str_offset(source, line) };
98
99			if end_offset < line_offset {
100				// Span is inside the previous line terminator, point to the end of the line.
101				let len = prev_line.chars().count();
102				break Self {
103					line: line_idx,
104					column: len + 1,
105				};
106			}
107
108			if (line_offset..(line_offset + line.len())).contains(&end_offset) {
109				let column_offset = end_offset - line_offset;
110				let column = line
111					.char_indices()
112					.enumerate()
113					.find(|(_, (char_idx, _))| *char_idx >= column_offset)
114					.map(|(l, _)| l)
115					.unwrap_or_else(|| {
116						// give up, just point to the end.
117						line.chars().count()
118					});
119				break Self {
120					line: line_idx + 1,
121					column: column + 1,
122				};
123			}
124
125			lines.next();
126			prev_line = line;
127		};
128
129		start..end
130	}
131}