surrealdb_core/syn/error/
location.rs

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