solar_interface/source_map/
file.rs1use crate::{BytePos, CharPos, SourceMap, pos::RelativeBytePos};
2use std::{
3 fmt, io,
4 ops::RangeInclusive,
5 path::{Path, PathBuf},
6 sync::Arc,
7};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub struct MultiByteChar {
12 pub pos: RelativeBytePos,
14 pub bytes: u8,
16}
17
18#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub enum FileName {
23 Real(PathBuf),
25 Stdin,
27 Custom(String),
29}
30
31impl PartialEq<Path> for FileName {
32 fn eq(&self, other: &Path) -> bool {
33 match self {
34 Self::Real(p) => p == other,
35 _ => false,
36 }
37 }
38}
39
40impl PartialEq<&Path> for FileName {
41 fn eq(&self, other: &&Path) -> bool {
42 match self {
43 Self::Real(p) => p == *other,
44 _ => false,
45 }
46 }
47}
48
49impl PartialEq<PathBuf> for FileName {
50 fn eq(&self, other: &PathBuf) -> bool {
51 match self {
52 Self::Real(p) => p == other,
53 _ => false,
54 }
55 }
56}
57
58impl From<PathBuf> for FileName {
59 fn from(p: PathBuf) -> Self {
60 Self::Real(p)
61 }
62}
63
64impl From<&PathBuf> for FileName {
65 fn from(p: &PathBuf) -> Self {
66 Self::Real(p.clone())
67 }
68}
69
70impl From<&Path> for FileName {
71 fn from(p: &Path) -> Self {
72 Self::Real(p.to_path_buf())
73 }
74}
75
76impl From<String> for FileName {
77 fn from(s: String) -> Self {
78 Self::Custom(s)
79 }
80}
81
82impl From<&Self> for FileName {
83 fn from(s: &Self) -> Self {
84 s.clone()
85 }
86}
87
88impl FileName {
89 pub fn real(path: impl Into<PathBuf>) -> Self {
91 Self::Real(path.into())
92 }
93
94 pub fn custom(s: impl Into<String>) -> Self {
96 Self::Custom(s.into())
97 }
98
99 #[inline]
101 pub fn display(&self) -> FileNameDisplay<'_> {
102 let sm = crate::SessionGlobals::try_with(|g| g.map(|g| g.source_map.clone()));
103 FileNameDisplay { inner: self, sm }
104 }
105
106 #[inline]
108 pub fn as_real(&self) -> Option<&Path> {
109 match self {
110 Self::Real(path) => Some(path),
111 _ => None,
112 }
113 }
114}
115
116pub struct FileNameDisplay<'a> {
117 inner: &'a FileName,
118 sm: Option<Arc<SourceMap>>,
119}
120
121impl fmt::Display for FileNameDisplay<'_> {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 match self.inner {
124 FileName::Real(path) => {
125 let path = if let Some(sm) = &self.sm
126 && let Some(base_path) = sm.base_path.get()
127 && let Ok(rpath) = path.strip_prefix(base_path)
128 {
129 rpath
130 } else {
131 path.as_path()
132 };
133 path.display().fmt(f)
134 }
135 FileName::Stdin => f.write_str("<stdin>"),
136 FileName::Custom(s) => write!(f, "<{s}>"),
137 }
138 }
139}
140
141#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
142pub struct StableSourceFileId(u64);
143
144impl StableSourceFileId {
145 pub(super) fn from_filename_in_current_crate(filename: &FileName) -> Self {
146 Self::new(
147 filename,
148 )
150 }
151
152 fn new(
160 filename: &FileName,
161 ) -> Self {
163 use std::hash::{Hash, Hasher};
164 let mut hasher = solar_data_structures::map::FxHasher::default();
165 filename.hash(&mut hasher);
166 Self(hasher.finish())
168 }
169}
170
171#[derive(Debug)]
173pub struct OffsetOverflowError(pub(crate) ());
174
175impl fmt::Display for OffsetOverflowError {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 f.write_str("files larger than 4GiB are not supported")
178 }
179}
180
181impl std::error::Error for OffsetOverflowError {}
182
183impl From<OffsetOverflowError> for io::Error {
184 fn from(e: OffsetOverflowError) -> Self {
185 Self::new(io::ErrorKind::FileTooLarge, e)
186 }
187}
188
189#[derive(Clone, derive_more::Debug)]
191pub struct SourceFile {
192 pub name: FileName,
196 #[debug(skip)]
198 pub src: Arc<String>,
199 #[debug(skip)]
201 pub src_hash: SourceFileHash,
202 pub start_pos: BytePos,
204 pub source_len: RelativeBytePos,
206 #[debug(skip)]
208 pub lines: Vec<RelativeBytePos>,
209 #[debug(skip)]
211 pub multibyte_chars: Vec<MultiByteChar>,
212 #[debug(skip)]
216 pub stable_id: StableSourceFileId,
217}
218
219impl SourceFile {
220 pub fn new(
221 name: FileName,
222 mut src: String,
223 hash_kind: SourceFileHashAlgorithm,
224 ) -> Result<Self, OffsetOverflowError> {
225 let src_hash = SourceFileHash::new(hash_kind, &src);
227 let stable_id = StableSourceFileId::from_filename_in_current_crate(&name);
230 let source_len = src.len();
231 let source_len = u32::try_from(source_len).map_err(|_| OffsetOverflowError(()))?;
232
233 let (lines, multibyte_chars) = super::analyze::analyze_source_file(&src);
234
235 src.shrink_to_fit();
236 Ok(Self {
237 name,
238 src: Arc::new(src),
239 src_hash,
240 start_pos: BytePos::from_u32(0),
241 source_len: RelativeBytePos::from_u32(source_len),
242 lines,
243 multibyte_chars,
244 stable_id,
245 })
246 }
247
248 pub fn lines(&self) -> &[RelativeBytePos] {
249 &self.lines
250 }
251
252 pub fn count_lines(&self) -> usize {
253 self.lines().len()
254 }
255
256 #[inline]
257 pub fn absolute_position(&self, pos: RelativeBytePos) -> BytePos {
258 BytePos::from_u32(pos.to_u32() + self.start_pos.to_u32())
259 }
260
261 #[inline]
262 pub fn relative_position(&self, pos: BytePos) -> RelativeBytePos {
263 RelativeBytePos::from_u32(pos.to_u32() - self.start_pos.to_u32())
264 }
265
266 #[inline]
267 pub fn end_position(&self) -> BytePos {
268 self.absolute_position(self.source_len)
269 }
270
271 pub fn lookup_line(&self, pos: RelativeBytePos) -> Option<usize> {
276 self.lines().partition_point(|x| x <= &pos).checked_sub(1)
277 }
278
279 pub fn line_position(&self, line_number: usize) -> Option<usize> {
282 self.lines().get(line_number).map(|x| x.to_usize())
283 }
284
285 pub(crate) fn bytepos_to_file_charpos(&self, bpos: RelativeBytePos) -> CharPos {
287 let mut total_extra_bytes = 0;
289
290 for mbc in self.multibyte_chars.iter() {
291 if mbc.pos < bpos {
292 total_extra_bytes += mbc.bytes as u32 - 1;
295 assert!(bpos.to_u32() >= mbc.pos.to_u32() + mbc.bytes as u32);
298 } else {
299 break;
300 }
301 }
302
303 assert!(total_extra_bytes <= bpos.to_u32());
304 CharPos(bpos.to_usize() - total_extra_bytes as usize)
305 }
306
307 fn lookup_file_pos(&self, pos: RelativeBytePos) -> (usize, CharPos) {
310 let chpos = self.bytepos_to_file_charpos(pos);
311 match self.lookup_line(pos) {
312 Some(a) => {
313 let line = a + 1; let linebpos = self.lines()[a];
315 let linechpos = self.bytepos_to_file_charpos(linebpos);
316 let col = chpos - linechpos;
317 assert!(chpos >= linechpos);
318 (line, col)
319 }
320 None => (0, chpos),
321 }
322 }
323
324 pub fn lookup_file_pos_with_col_display(&self, pos: BytePos) -> (usize, CharPos, usize) {
327 let pos = self.relative_position(pos);
328 let (line, col_or_chpos) = self.lookup_file_pos(pos);
329 if line > 0 {
330 let Some(code) = self.get_line(line - 1) else {
331 debug!("couldn't find line {line} in {:?}", self.name);
339 return (line, col_or_chpos, col_or_chpos.0);
340 };
341 let display_col = code.chars().take(col_or_chpos.0).map(char_width).sum();
342 (line, col_or_chpos, display_col)
343 } else {
344 (0, col_or_chpos, col_or_chpos.0)
346 }
347 }
348
349 pub fn get_line(&self, line_number: usize) -> Option<&str> {
352 fn get_until_newline(src: &str, begin: usize) -> &str {
353 let slice = &src[begin..];
357 match slice.find('\n') {
358 Some(e) => &slice[..e],
359 None => slice,
360 }
361 }
362
363 let start = self.lines().get(line_number)?.to_usize();
364 Some(get_until_newline(&self.src, start))
365 }
366
367 pub fn get_lines(&self, range: RangeInclusive<usize>) -> Option<&str> {
370 fn get_until_newline(src: &str, start: usize, end: usize) -> &str {
371 match src[end..].find('\n') {
372 Some(e) => &src[start..end + e + 1],
373 None => &src[start..],
374 }
375 }
376
377 let (start, end) = range.into_inner();
378 let lines = self.lines();
379 let start = lines.get(start)?.to_usize();
380 let end = lines.get(end)?.to_usize();
381 Some(get_until_newline(&self.src, start, end))
382 }
383
384 #[inline]
389 pub fn contains(&self, byte_pos: BytePos) -> bool {
390 byte_pos >= self.start_pos && byte_pos <= self.end_position()
391 }
392
393 #[inline]
394 pub fn is_empty(&self) -> bool {
395 self.source_len.to_u32() == 0
396 }
397
398 pub fn original_relative_byte_pos(&self, pos: BytePos) -> RelativeBytePos {
401 let pos = self.relative_position(pos);
402 RelativeBytePos::from_u32(pos.0)
403 }
404}
405
406pub fn char_width(ch: char) -> usize {
407 match ch {
408 '\t' => 4,
409 _ => unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1),
410 }
411}
412
413#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
414pub enum SourceFileHashAlgorithm {
415 #[default]
416 None,
417 }
421
422impl std::str::FromStr for SourceFileHashAlgorithm {
423 type Err = ();
424
425 fn from_str(s: &str) -> Result<Self, Self::Err> {
426 let _ = s;
433 Err(())
434 }
435}
436
437impl SourceFileHashAlgorithm {
438 #[inline]
440 pub const fn hash_len(self) -> usize {
441 match self {
442 Self::None => 0,
443 }
447 }
448}
449
450const MAX_HASH_SIZE: usize = 32;
451
452#[derive(Clone, Copy, PartialEq, Eq, Hash)]
454pub struct SourceFileHash {
455 kind: SourceFileHashAlgorithm,
456 value: [u8; MAX_HASH_SIZE],
457}
458
459impl fmt::Debug for SourceFileHash {
460 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461 let mut dbg = f.debug_struct("SourceFileHash");
462 dbg.field("kind", &self.kind);
463 if self.kind != SourceFileHashAlgorithm::None {
464 dbg.field("value", &format_args!("{}", hex::encode(self.hash_bytes())));
465 }
466 dbg.finish()
467 }
468}
469
470impl SourceFileHash {
471 pub fn new(kind: SourceFileHashAlgorithm, src: &str) -> Self {
472 let _ = src;
491 Self { kind, value: Default::default() }
492 }
493
494 pub fn matches(&self, src: &str) -> bool {
496 Self::new(self.kind, src).hash_bytes() == self.hash_bytes()
497 }
498
499 pub fn hash_bytes(&self) -> &[u8] {
501 &self.value[..self.hash_len()]
502 }
503
504 pub const fn kind(&self) -> SourceFileHashAlgorithm {
506 self.kind
507 }
508
509 #[inline]
511 pub const fn hash_len(&self) -> usize {
512 self.kind.hash_len()
513 }
514}