Skip to main content

rucc_diag/
source.rs

1//! The source map: which file a [`BytePos`] lands in, and where in that file.
2//!
3//! Design: `spec/03-architecture.md`, and `spec/05-preprocessor.md` section 5.2 for the
4//! coordinate space a token span lives in.
5//!
6//! Every file in a translation unit gets a range of one flat coordinate space, so a [`Span`]
7//! is two integers and comparing two of them does not need to know which file each came from.
8//! That is what keeps a token at sixteen bytes with a header nest twelve deep, and it is why
9//! the expander can join the span of a macro argument with the span of the call site without
10//! a special case.
11//!
12//! The price is that turning an offset back into a file, a line and a column is a search
13//! rather than a field read. It is paid only when a diagnostic is rendered, which happens for
14//! a handful of positions out of the millions the lexer produces, so the line table for a
15//! file is built the first time somebody asks about that file and never for a file nobody
16//! asks about.
17//!
18//! ```
19//! use rucc_diag::SourceMap;
20//!
21//! let mut map = SourceMap::new();
22//! let file = map.add("hello.c", b"int main(void)\n{\n    return 0;\n}\n".to_vec()).unwrap();
23//! let brace = map.file(file).start + 15;
24//! let loc = map.lookup(brace).unwrap();
25//! assert_eq!(loc.line, 2);
26//! assert_eq!(loc.column, 1);
27//! assert_eq!(map.render_position(brace), "hello.c:2:1");
28//! ```
29
30use std::fmt;
31use std::sync::{Arc, OnceLock};
32
33use crate::{BytePos, Span};
34
35/// The contents of a file, shared rather than copied.
36///
37/// A trait object rather than a `Vec`, so that the memory mapped input in
38/// `spec/05-preprocessor.md` section 5.2 can be handed over as it is, and shared so that a
39/// header included twice, or served twice out of the header cache, is held once.
40///
41/// It is a type of its own rather than a bare `Arc` so that it can have a `Debug` that says
42/// how long a file is instead of printing it. A `{:#?}` of anything holding one of these
43/// should not dump the whole of `stdio.h` into a test failure.
44#[derive(Clone)]
45pub struct SourceBytes(Arc<dyn AsRef<[u8]> + Send + Sync>);
46
47impl SourceBytes {
48    /// Takes ownership of anything that is a slice of bytes.
49    pub fn new(bytes: impl AsRef<[u8]> + Send + Sync + 'static) -> SourceBytes {
50        SourceBytes(Arc::new(bytes))
51    }
52
53    /// The bytes.
54    #[inline]
55    pub fn as_slice(&self) -> &[u8] {
56        (*self.0).as_ref()
57    }
58}
59
60impl fmt::Debug for SourceBytes {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        write!(f, "SourceBytes({} bytes)", self.as_slice().len())
63    }
64}
65
66impl AsRef<[u8]> for SourceBytes {
67    #[inline]
68    fn as_ref(&self) -> &[u8] {
69        self.as_slice()
70    }
71}
72
73impl std::ops::Deref for SourceBytes {
74    type Target = [u8];
75
76    #[inline]
77    fn deref(&self) -> &[u8] {
78        self.as_slice()
79    }
80}
81
82/// A file in the source map.
83///
84/// Only meaningful against the map that issued it. A `FileId` is an index, so passing one to
85/// a different map is a bug the type system does not catch, which is fine because there is
86/// one map per compilation and it lives on the session.
87#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
88pub struct FileId(u32);
89
90impl FileId {
91    /// The index of this file in the map, for a caller keeping a side table.
92    #[inline]
93    pub const fn index(self) -> usize {
94        self.0 as usize
95    }
96}
97
98/// A position resolved back to a human coordinate.
99///
100/// Lines and columns both count from one, because that is what every editor, every other
101/// compiler and every user expects, and an off by one here is the kind of bug that survives
102/// for years because nobody quite trusts their own arithmetic enough to file it.
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct Loc {
105    /// Which file.
106    pub file: FileId,
107    /// The line, counting from one.
108    pub line: u32,
109    /// The column in bytes, counting from one.
110    ///
111    /// Bytes rather than characters or display columns. A tab counts as one and a multibyte
112    /// character counts as its encoded length, which is what a caret line drawn from the same
113    /// bytes needs. `-ftabstop` and the width of a CJK character are the renderer's problem;
114    /// what belongs here is the offset into the line.
115    pub column: u32,
116}
117
118/// The bytes of one file, plus where they sit in the flat space.
119///
120/// Contents are held behind a trait object rather than as a `Vec`, so that the memory mapped
121/// input in `spec/05-preprocessor.md` section 5.2 can be handed over as it is instead of
122/// being copied into one. Reading the bytes goes through one virtual call, which is fine
123/// because it happens once per file in the lexer and once per rendered diagnostic, never in
124/// a loop.
125pub struct SourceFile {
126    /// This file's own id, so that anything holding a `&SourceFile` can name it.
127    pub id: FileId,
128    /// The name to print in a diagnostic, which is the path as the user wrote it rather than
129    /// a canonical one. Somebody who typed `-I../include` wants to read `../include/foo.h`.
130    pub name: String,
131    /// First byte of this file in the flat space.
132    pub start: BytePos,
133    /// One past this file's last byte.
134    pub end: BytePos,
135    /// The `#include` that pulled this file in, or `None` for a file named on the command
136    /// line. This is what "in file included from" is printed from.
137    pub included_from: Option<Span>,
138    bytes: SourceBytes,
139    /// Absolute offset of the first byte of each line. Built on first use, because most files
140    /// in a build are never the subject of a diagnostic.
141    lines: OnceLock<Vec<BytePos>>,
142    /// The `#line` directives in this file, in the order they were read, which is also the
143    /// order of the positions they start at. Empty for almost every file there is.
144    presumed: Vec<Presumed>,
145}
146
147/// What a stretch of a file is presented as, which is what a `#line` in front of it said.
148///
149/// A `#line` does not move any bytes. It changes the answer to "where is this", for
150/// `__FILE__` and `__LINE__`, for a diagnostic and for the markers `-E` writes, and for
151/// nothing else: the text of the line is still read out of the real file at the real offset,
152/// because that is where the characters are.
153#[derive(Debug, Clone)]
154struct Presumed {
155    /// First byte of the line this applies from, which is the line after the directive.
156    at: BytePos,
157    /// The real line `at` is on, so the distance from it can be added back on.
158    real: u32,
159    /// The line `at` is presented as.
160    line: u32,
161    /// The name the file is presented under from `at` on. A `#line` with no name carries the
162    /// one already in force, so this is never empty and the lookup never has to walk back.
163    name: String,
164}
165
166/// Where a position is presented as being, which is where a `#line` says it is.
167///
168/// The name is borrowed rather than owned because the common case is that it is the file's
169/// own name and there is nothing to clone.
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct PresumedLoc<'a> {
172    /// The file name to print.
173    pub name: &'a str,
174    /// The line to print, counting from one.
175    pub line: u32,
176    /// The column, which no `#line` ever changes.
177    pub column: u32,
178}
179
180impl fmt::Debug for SourceFile {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        // The bytes are deliberately not printed. A `{:#?}` of a session should not dump the
183        // whole of `stdio.h` into a test failure.
184        f.debug_struct("SourceFile")
185            .field("id", &self.id)
186            .field("name", &self.name)
187            .field("start", &self.start)
188            .field("end", &self.end)
189            .field("included_from", &self.included_from)
190            .finish()
191    }
192}
193
194impl SourceFile {
195    /// The file's contents.
196    #[inline]
197    pub fn bytes(&self) -> &[u8] {
198        self.bytes.as_slice()
199    }
200
201    /// The file's contents, shared.
202    #[inline]
203    pub fn shared_bytes(&self) -> SourceBytes {
204        self.bytes.clone()
205    }
206
207    /// Length in bytes.
208    #[inline]
209    pub fn len(&self) -> u32 {
210        self.end - self.start
211    }
212
213    /// Whether the file is empty.
214    #[inline]
215    pub fn is_empty(&self) -> bool {
216        self.start == self.end
217    }
218
219    /// Whether `pos` falls in this file.
220    ///
221    /// The end position is included, so a diagnostic about something missing at the end of a
222    /// file still names the file rather than falling into the gap after it.
223    #[inline]
224    pub fn contains(&self, pos: BytePos) -> bool {
225        self.start <= pos && pos <= self.end
226    }
227
228    /// How many lines the file has, counting a trailing newline as ending the last line
229    /// rather than starting another. An empty file has one line, which is empty.
230    pub fn line_count(&self) -> u32 {
231        u32::try_from(self.lines().len()).unwrap_or(u32::MAX)
232    }
233
234    /// The bytes of line `line`, counting from one, without its line terminator.
235    ///
236    /// `None` if the file has no such line.
237    pub fn line_bytes(&self, line: u32) -> Option<&[u8]> {
238        let lines = self.lines();
239        let index = usize::try_from(line.checked_sub(1)?).ok()?;
240        let from = *lines.get(index)? - self.start;
241        let to = lines.get(index + 1).map_or(self.len(), |next| *next - self.start);
242        let text = self.bytes().get(from as usize..to as usize)?;
243        // Strip the terminator rather than the last byte, so that a file with CRLF endings
244        // does not put a carriage return in the middle of a rendered caret line.
245        let text = text.strip_suffix(b"\n").unwrap_or(text);
246        Some(text.strip_suffix(b"\r").unwrap_or(text))
247    }
248
249    /// The line and column of `pos`, or `None` if `pos` is not in this file.
250    pub fn position(&self, pos: BytePos) -> Option<Loc> {
251        let (line, begin) = self.line_of(pos)?;
252        Some(Loc { file: self.id, line, column: pos - begin + 1 })
253    }
254
255    /// Where `pos` is presented as being, once the `#line` directives in front of it are
256    /// taken into account. The same as [`SourceFile::position`] for a file that has none.
257    pub fn presumed_position(&self, pos: BytePos) -> Option<PresumedLoc<'_>> {
258        let loc = self.position(pos)?;
259        // The entries are in increasing order of `at`, so the one in force is the last one
260        // starting at or before `pos`.
261        let after = self.presumed.partition_point(|p| p.at <= pos);
262        let Some(entry) = after.checked_sub(1).map(|i| &self.presumed[i]) else {
263            return Some(PresumedLoc { name: &self.name, line: loc.line, column: loc.column });
264        };
265        // `pos` is at or after `entry.at`, so the subtraction cannot go below zero.
266        let line = entry.line.saturating_add(loc.line - entry.real);
267        Some(PresumedLoc { name: &entry.name, line, column: loc.column })
268    }
269
270    /// The span covering the line `pos` is on, including its terminator.
271    pub fn line_span(&self, pos: BytePos) -> Option<Span> {
272        let (line, begin) = self.line_of(pos)?;
273        let end = self.lines().get(line as usize).copied().unwrap_or(self.end);
274        Some(Span::new(begin, end))
275    }
276
277    /// The one-based line `pos` is on, and where that line starts.
278    fn line_of(&self, pos: BytePos) -> Option<(u32, BytePos)> {
279        if !self.contains(pos) {
280            return None;
281        }
282        let lines = self.lines();
283        // `partition_point` gives the number of line starts at or before `pos`, which is the
284        // one-based line number, and is never zero because the first entry is the file start.
285        let line = lines.partition_point(|&start| start <= pos);
286        let begin = lines.get(line.saturating_sub(1)).copied().unwrap_or(self.start);
287        Some((u32::try_from(line).unwrap_or(u32::MAX), begin))
288    }
289
290    /// The line start table, built on first use.
291    fn lines(&self) -> &[BytePos] {
292        self.lines.get_or_init(|| {
293            let bytes = self.bytes();
294            // Twenty four bytes a line is roughly what C source averages. Getting this wrong
295            // costs a reallocation, not a correctness problem.
296            let mut starts = Vec::with_capacity(bytes.len() / 24 + 1);
297            starts.push(self.start);
298            for (at, _) in bytes.iter().enumerate().filter(|&(_, &b)| b == b'\n') {
299                let next = self.start + u32::try_from(at).unwrap_or(u32::MAX - 1) + 1;
300                // A newline as the very last byte ends the last line, it does not open an
301                // empty one. Every other newline opens a line, including one followed
302                // immediately by another newline.
303                if next < self.end {
304                    starts.push(next);
305                }
306            }
307            starts
308        })
309    }
310}
311
312/// The flat coordinate space is full.
313///
314/// Reaching this needs four gigabytes of source in one translation unit, counting every
315/// header once per time it is included. It is reported rather than ignored because the
316/// alternative is spans that silently point at the wrong file.
317#[derive(Debug, Clone, Copy, PartialEq, Eq)]
318pub struct SourceMapFull;
319
320impl fmt::Display for SourceMapFull {
321    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322        f.write_str("the translation unit does not fit in the four gigabyte source map")
323    }
324}
325
326impl std::error::Error for SourceMapFull {}
327
328/// Every file of one translation unit, laid end to end.
329#[derive(Debug, Default)]
330pub struct SourceMap {
331    files: Vec<SourceFile>,
332    next: BytePos,
333}
334
335impl SourceMap {
336    /// An empty map.
337    pub fn new() -> SourceMap {
338        SourceMap::default()
339    }
340
341    /// Adds a file named on the command line.
342    ///
343    /// # Errors
344    ///
345    /// [`SourceMapFull`] if the file does not fit in what is left of the coordinate space.
346    pub fn add(
347        &mut self,
348        name: impl Into<String>,
349        bytes: impl AsRef<[u8]> + Send + Sync + 'static,
350    ) -> Result<FileId, SourceMapFull> {
351        self.push(name.into(), SourceBytes::new(bytes), None)
352    }
353
354    /// Adds a file whose contents are already shared.
355    ///
356    /// This is the entry point the file system abstraction uses, because it hands out bytes
357    /// it may also be holding in a cache.
358    ///
359    /// # Errors
360    ///
361    /// [`SourceMapFull`] if the file does not fit in what is left of the coordinate space.
362    pub fn add_shared(
363        &mut self,
364        name: impl Into<String>,
365        bytes: SourceBytes,
366        included_from: Option<Span>,
367    ) -> Result<FileId, SourceMapFull> {
368        self.push(name.into(), bytes, included_from)
369    }
370
371    /// Adds a file reached through the `#include` at `from`.
372    ///
373    /// # Errors
374    ///
375    /// [`SourceMapFull`] if the file does not fit in what is left of the coordinate space.
376    pub fn add_included(
377        &mut self,
378        name: impl Into<String>,
379        bytes: impl AsRef<[u8]> + Send + Sync + 'static,
380        from: Span,
381    ) -> Result<FileId, SourceMapFull> {
382        self.push(name.into(), SourceBytes::new(bytes), Some(from))
383    }
384
385    fn push(
386        &mut self,
387        name: String,
388        bytes: SourceBytes,
389        included_from: Option<Span>,
390    ) -> Result<FileId, SourceMapFull> {
391        let len = u32::try_from(bytes.as_slice().len()).map_err(|_| SourceMapFull)?;
392        let start = self.next;
393        let end = start.checked_add(len).ok_or(SourceMapFull)?;
394        // One byte of padding after every file, so that the position one past the end of a
395        // file is still that file's and not the first byte of the next one. Without it a
396        // diagnostic about a missing `}` at the end of a header names whatever came after it.
397        // `BytePos::MAX` is `Span::DUMMY` and belongs to nobody, so the space stops one short.
398        self.next = end.checked_add(1).filter(|&n| n < BytePos::MAX).ok_or(SourceMapFull)?;
399        let id = FileId(u32::try_from(self.files.len()).map_err(|_| SourceMapFull)?);
400        self.files.push(SourceFile {
401            id,
402            name,
403            start,
404            end,
405            included_from,
406            bytes,
407            lines: OnceLock::new(),
408            presumed: Vec::new(),
409        });
410        Ok(id)
411    }
412
413    /// Every file, in the order they were added.
414    pub fn files(&self) -> &[SourceFile] {
415        &self.files
416    }
417
418    /// The file `id` names.
419    ///
420    /// # Panics
421    ///
422    /// Panics if `id` came from a different map. There is one map per compilation, on the
423    /// session, so this is a programming error rather than something a caller handles.
424    pub fn file(&self, id: FileId) -> &SourceFile {
425        &self.files[id.index()]
426    }
427
428    /// Which file `pos` is in.
429    pub fn lookup_file(&self, pos: BytePos) -> Option<FileId> {
430        if pos == BytePos::MAX {
431            return None;
432        }
433        // Files are laid out in increasing order and never overlap, so the candidate is the
434        // last one starting at or before `pos`. It is a candidate rather than the answer
435        // because `pos` may be in the padding byte after that file.
436        let at = self.files.partition_point(|f| f.start <= pos);
437        let file = self.files.get(at.checked_sub(1)?)?;
438        file.contains(pos).then_some(file.id)
439    }
440
441    /// The file, line and column of `pos`.
442    pub fn lookup(&self, pos: BytePos) -> Option<Loc> {
443        self.file(self.lookup_file(pos)?).position(pos)
444    }
445
446    /// Where `pos` is presented as being, which is [`SourceMap::lookup`] with the `#line`
447    /// directives in front of it applied.
448    ///
449    /// This is the answer to give a user: it is what a diagnostic prints, what `__FILE__` and
450    /// `__LINE__` expand to and what a line marker says. [`SourceMap::lookup`] is the answer
451    /// to use when the bytes are wanted, which is reading the text of a line to draw a caret
452    /// under it.
453    pub fn presumed(&self, pos: BytePos) -> Option<PresumedLoc<'_>> {
454        self.file(self.lookup_file(pos)?).presumed_position(pos)
455    }
456
457    /// Where the line after the one `at` is on is presented as being.
458    ///
459    /// This is what a `#line` written at `at` did, asked after the fact. `-E` needs it to
460    /// write the marker the directive turns into, and asking it here rather than reading the
461    /// directive again is what keeps one answer about where anything is.
462    pub fn presumed_after(&self, at: BytePos) -> Option<PresumedLoc<'_>> {
463        let file = self.file(self.lookup_file(at)?);
464        file.presumed_position(file.line_span(at)?.hi)
465    }
466
467    /// Records a `#line` written at `at`, which presents the line after it as `line`, and the
468    /// file as `name` when one was given.
469    ///
470    /// The directive applies from the following line rather than from where it is written,
471    /// which is what makes `#line 1000` followed by `__LINE__` expand to 1000 and not 1001.
472    /// A `#line` with no name leaves the name alone, so the entry inherits whichever one is
473    /// already in force.
474    ///
475    /// Nothing happens if `at` is in no file, or if the directive is the last line of one.
476    /// There is nothing after it for the entry to apply to in either case.
477    pub fn set_presumed(&mut self, at: BytePos, line: u32, name: Option<String>) {
478        let Some(id) = self.lookup_file(at) else { return };
479        let Some(from) = self.file(id).line_span(at).map(|l| l.hi) else { return };
480        let Some(real) = self.file(id).position(from).map(|loc| loc.line) else { return };
481        let name = name.unwrap_or_else(|| {
482            let file = self.file(id);
483            file.presumed.last().map_or_else(|| file.name.clone(), |p| p.name.clone())
484        });
485        let file = &mut self.files[id.index()];
486        // A directive read twice is a file included twice, and a file included twice is two
487        // files in this map. So the entries only ever arrive in increasing order of `at`,
488        // which is the order the lookup binary searches in.
489        file.presumed.push(Presumed { at: from, real, line, name });
490    }
491
492    /// `name:line:column` for `pos`, or `<unknown>` for a position in no file.
493    ///
494    /// This is the prefix of a rendered diagnostic and the form every editor already knows
495    /// how to jump to.
496    pub fn render_position(&self, pos: BytePos) -> String {
497        match self.presumed(pos) {
498            Some(loc) => format!("{}:{}:{}", loc.name, loc.line, loc.column),
499            None => "<unknown>".to_owned(),
500        }
501    }
502
503    /// The chain of `#include` directives that led to `pos`, innermost first.
504    ///
505    /// Empty for a position in a file named on the command line. This is what the "in file
506    /// included from" block of a diagnostic is printed from, and reading it out of the map
507    /// rather than out of a stack the preprocessor keeps means it is still available long
508    /// after preprocessing has finished.
509    pub fn include_stack(&self, pos: BytePos) -> Vec<Span> {
510        let mut stack = Vec::new();
511        let mut at = self.lookup_file(pos);
512        while let Some(file) = at {
513            let Some(from) = self.file(file).included_from else { break };
514            stack.push(from);
515            at = self.lookup_file(from.lo);
516            // A file is always added after the one that includes it, so the walk terminates.
517            // A map built by hand in a test could say otherwise, and an infinite loop inside
518            // the diagnostic renderer is a bad way to find that out.
519            if stack.len() > self.files.len() {
520                break;
521            }
522        }
523        stack
524    }
525
526    /// How much of the coordinate space is used, which is where the next file will start.
527    pub fn used(&self) -> BytePos {
528        self.next
529    }
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535
536    fn map_with(files: &[(&str, &str)]) -> (SourceMap, Vec<FileId>) {
537        let mut map = SourceMap::new();
538        let ids = files
539            .iter()
540            .map(|(name, text)| map.add(*name, text.as_bytes().to_vec()).unwrap())
541            .collect();
542        (map, ids)
543    }
544
545    #[test]
546    fn the_first_file_starts_at_zero_and_the_next_one_after_a_gap() {
547        let (map, ids) = map_with(&[("a.c", "ab"), ("b.c", "cd")]);
548        assert_eq!(map.file(ids[0]).start, 0);
549        assert_eq!(map.file(ids[0]).end, 2);
550        assert_eq!(map.file(ids[1]).start, 3);
551        assert_eq!(map.used(), 6);
552    }
553
554    #[test]
555    fn the_position_after_a_file_belongs_to_that_file_and_not_the_next() {
556        let (map, ids) = map_with(&[("a.c", "ab"), ("b.c", "cd")]);
557        assert_eq!(map.lookup_file(2), Some(ids[0]));
558        assert_eq!(map.lookup_file(3), Some(ids[1]));
559    }
560
561    #[test]
562    fn a_position_in_the_gap_is_in_no_file() {
563        let mut map = SourceMap::new();
564        map.add("a.c", b"ab".to_vec()).unwrap();
565        // Offset 2 is the end of `a.c`, and offset 3 would be the next file, which does not
566        // exist, so nothing is there.
567        assert_eq!(map.lookup_file(3), None);
568        assert_eq!(map.render_position(3), "<unknown>");
569    }
570
571    #[test]
572    fn a_dummy_span_resolves_to_nothing() {
573        let (map, _) = map_with(&[("a.c", "ab")]);
574        assert_eq!(map.lookup(Span::DUMMY.lo), None);
575        assert_eq!(map.lookup_file(BytePos::MAX), None);
576    }
577
578    #[test]
579    fn lines_and_columns_count_from_one() {
580        let (map, ids) = map_with(&[("a.c", "one\ntwo\nthree\n")]);
581        let start = map.file(ids[0]).start;
582        assert_eq!(map.lookup(start).unwrap(), Loc { file: ids[0], line: 1, column: 1 });
583        assert_eq!(map.lookup(start + 4).unwrap(), Loc { file: ids[0], line: 2, column: 1 });
584        assert_eq!(map.lookup(start + 6).unwrap(), Loc { file: ids[0], line: 2, column: 3 });
585        assert_eq!(map.render_position(start + 8), "a.c:3:1");
586    }
587
588    #[test]
589    fn a_trailing_newline_does_not_open_a_line() {
590        let (map, ids) = map_with(&[("a.c", "one\ntwo\n"), ("b.c", "one\ntwo")]);
591        assert_eq!(map.file(ids[0]).line_count(), 2);
592        assert_eq!(map.file(ids[1]).line_count(), 2);
593    }
594
595    #[test]
596    fn a_blank_line_is_a_line() {
597        let (map, ids) = map_with(&[("a.c", "one\n\nthree\n")]);
598        let file = map.file(ids[0]);
599        assert_eq!(file.line_count(), 3);
600        assert_eq!(file.line_bytes(2), Some(&b""[..]));
601        assert_eq!(file.line_bytes(3), Some(&b"three"[..]));
602        assert_eq!(file.line_bytes(4), None);
603        assert_eq!(file.line_bytes(0), None);
604    }
605
606    #[test]
607    fn a_carriage_return_is_not_part_of_the_line() {
608        let (map, ids) = map_with(&[("a.c", "one\r\ntwo\r\n")]);
609        let file = map.file(ids[0]);
610        assert_eq!(file.line_bytes(1), Some(&b"one"[..]));
611        assert_eq!(file.line_bytes(2), Some(&b"two"[..]));
612    }
613
614    #[test]
615    fn an_empty_file_has_one_position_and_no_lines_to_read() {
616        let (map, ids) = map_with(&[("a.c", "")]);
617        let file = map.file(ids[0]);
618        assert!(file.is_empty());
619        assert_eq!(map.lookup(file.start).unwrap().line, 1);
620        assert_eq!(file.line_bytes(1), Some(&b""[..]));
621        assert_eq!(file.line_bytes(2), None);
622    }
623
624    #[test]
625    fn a_line_span_covers_the_terminator() {
626        let (map, ids) = map_with(&[("a.c", "one\ntwo\n")]);
627        let file = map.file(ids[0]);
628        assert_eq!(file.line_span(file.start + 1), Some(Span::new(0, 4)));
629        assert_eq!(file.line_span(file.start + 5), Some(Span::new(4, 8)));
630    }
631
632    #[test]
633    fn the_include_stack_runs_from_the_innermost_out() {
634        let mut map = SourceMap::new();
635        let main = map.add("main.c", b"#include <a.h>\n".to_vec()).unwrap();
636        let outer = Span::new(map.file(main).start, map.file(main).start + 14);
637        let a = map.add_included("a.h", b"#include <b.h>\n".to_vec(), outer).unwrap();
638        let inner = Span::new(map.file(a).start, map.file(a).start + 14);
639        let b = map.add_included("b.h", b"int x;\n".to_vec(), inner).unwrap();
640        let stack = map.include_stack(map.file(b).start);
641        assert_eq!(stack, vec![inner, outer]);
642        assert_eq!(map.lookup(stack[0].lo).unwrap().file, a);
643        assert_eq!(map.lookup(stack[1].lo).unwrap().file, main);
644        assert!(map.include_stack(outer.lo).is_empty());
645    }
646
647    #[test]
648    fn a_file_that_does_not_fit_is_refused_rather_than_wrapped() {
649        let mut map = SourceMap::new();
650        map.add("a.c", b"x".to_vec()).unwrap();
651        // The map is then asked for everything that is left plus the padding it always adds,
652        // which is one byte more than the space holds.
653        map.next = BytePos::MAX - 2;
654        assert_eq!(map.add("b.c", b"xx".to_vec()), Err(SourceMapFull));
655        assert_eq!(map.files().len(), 1);
656    }
657
658    #[test]
659    fn contents_can_be_anything_that_is_a_slice_of_bytes() {
660        // What a memory mapped file will look like when it arrives: not a `Vec`, not a
661        // `String`, just something that hands out a slice.
662        struct Mapped(&'static [u8]);
663        impl AsRef<[u8]> for Mapped {
664            fn as_ref(&self) -> &[u8] {
665                self.0
666            }
667        }
668        let mut map = SourceMap::new();
669        let id = map.add("a.c", Mapped(b"int x;\n")).unwrap();
670        assert_eq!(map.file(id).bytes(), b"int x;\n");
671        assert_eq!(map.file(id).line_count(), 1);
672    }
673
674    /// Position of the first byte of `line` in the only file of `map`.
675    fn start_of(map: &SourceMap, line: u32) -> BytePos {
676        let file = &map.files()[0];
677        let mut at = file.start;
678        for _ in 1..line {
679            at = file.line_span(at).expect("the file has that line").hi;
680        }
681        at
682    }
683
684    #[test]
685    fn a_line_directive_moves_the_lines_after_it_and_not_the_ones_before() {
686        let (mut map, _) = map_with(&[("a.c", "one\n#line 90\nthree\nfour\n")]);
687        map.set_presumed(start_of(&map, 2), 90, None);
688        assert_eq!(map.render_position(start_of(&map, 1)), "a.c:1:1");
689        assert_eq!(map.render_position(start_of(&map, 2)), "a.c:2:1");
690        assert_eq!(map.render_position(start_of(&map, 3)), "a.c:90:1");
691        assert_eq!(map.render_position(start_of(&map, 4)), "a.c:91:1");
692    }
693
694    #[test]
695    fn a_directive_with_a_name_renames_the_file_and_one_without_leaves_the_name() {
696        let (mut map, _) = map_with(&[("a.c", "one\n#line 90 \"gen.y\"\nthree\n#line 5\nfive\n")]);
697        map.set_presumed(start_of(&map, 2), 90, Some("gen.y".to_owned()));
698        map.set_presumed(start_of(&map, 4), 5, None);
699        assert_eq!(map.render_position(start_of(&map, 3)), "gen.y:90:1");
700        assert_eq!(map.render_position(start_of(&map, 5)), "gen.y:5:1");
701    }
702
703    #[test]
704    fn the_directive_says_where_the_line_after_it_is() {
705        let (mut map, _) = map_with(&[("a.c", "one\n#line 90 \"gen.y\"\nthree\n")]);
706        let directive = start_of(&map, 2);
707        map.set_presumed(directive, 90, Some("gen.y".to_owned()));
708        let after = map.presumed_after(directive).expect("the directive is in the file");
709        assert_eq!((after.name, after.line), ("gen.y", 90));
710    }
711
712    #[test]
713    fn the_bytes_of_a_line_are_still_read_from_where_the_bytes_are() {
714        // The whole risk of presenting a different line is that something goes looking for the
715        // text at the presented one and draws a caret under nothing.
716        let (mut map, ids) = map_with(&[("a.c", "one\n#line 90\nthree\n")]);
717        map.set_presumed(start_of(&map, 2), 90, None);
718        let at = start_of(&map, 3);
719        assert_eq!(map.lookup(at).expect("in the file").line, 3);
720        assert_eq!(map.file(ids[0]).line_bytes(3), Some(&b"three"[..]));
721    }
722}