Skip to main content

rucc_pp/
print.rs

1//! Printing the token stream back out, which is what `-E` writes.
2//!
3//! Design: `spec/05-preprocessor.md` section 5.6.
4//!
5//! Two rules decide everything here, and they pull against each other. The output has to be
6//! usable as input, so two tokens that would lex as one token when written next to each other
7//! get a space between them. And the output has to be diffable against GCC's, because that
8//! diff is the fastest way to find a preprocessor bug, so the line structure, the indentation
9//! and the line markers all follow GCC rather than being tidied up.
10//!
11//! The line marker format is GCC's: `# 42 "file.h" 1` where the number after the name is 1 for
12//! entering a file, 2 for returning to one, 3 for a system header and 4 for a header whose
13//! contents are implicitly `extern "C"`. A gap of up to eight lines is printed as blank lines
14//! rather than as a marker, which is what GCC does and what keeps the output readable.
15
16use rucc_base::Interner;
17use rucc_diag::{FileId, SourceMap};
18use rucc_lex::{PpTokenKind, Punct, TokenFlags};
19
20use crate::directive::LineDirective;
21use crate::include::{quoted, spelling};
22use crate::token::Tok;
23
24/// How many blank lines are worth printing before a line marker is cheaper.
25///
26/// GCC's number. It is not tuned for anything, but matching it is the difference between an
27/// empty diff and a diff on every header boundary.
28const MAX_BLANKS: u32 = 8;
29
30/// What `-E` was asked for.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct PrintOptions {
33    /// Whether to write line markers, which `-P` turns off.
34    ///
35    /// With them off the blank line padding goes too, because the point of `-P` is output for
36    /// something other than a compiler to read.
37    pub line_markers: bool,
38}
39
40impl PrintOptions {
41    /// The default, which is what plain `-E` asks for.
42    pub fn new() -> PrintOptions {
43        PrintOptions { line_markers: true }
44    }
45}
46
47impl Default for PrintOptions {
48    fn default() -> PrintOptions {
49        PrintOptions::new()
50    }
51}
52
53/// Renders `tokens` the way `-E` prints them.
54///
55/// `main` is the file named on the command line, which is what the first line marker says even
56/// when the first token comes from a header. `lines` is the `#line` directives the run read,
57/// in the order it read them, because each one is a marker in the output at the point it was
58/// written rather than at the point its effect is first visible.
59pub fn print(
60    main: FileId,
61    tokens: &[Tok],
62    lines: &[LineDirective],
63    sources: &SourceMap,
64    interner: &Interner,
65    opts: PrintOptions,
66) -> String {
67    let mut printer = Printer {
68        out: String::new(),
69        opts,
70        sources,
71        interner,
72        file: main,
73        name: sources.file(main).name.clone(),
74        line: 1,
75        printed: false,
76        stack: vec![main],
77        lines,
78        next: 0,
79        directive: false,
80    };
81    printer.start();
82    let mut previous: Option<Tok> = None;
83    for (at, &tok) in tokens.iter().enumerate() {
84        printer.line_directives(at);
85        printer.token(tok, previous);
86        previous = Some(tok);
87    }
88    printer.line_directives(tokens.len());
89    printer.finish()
90}
91
92/// The state of the output: which file and line it is standing on.
93struct Printer<'a> {
94    out: String,
95    opts: PrintOptions,
96    sources: &'a SourceMap,
97    interner: &'a Interner,
98    /// The file the output is currently in.
99    file: FileId,
100    /// The name that file is going under, which a `#line` can change without the output
101    /// leaving the file. It is held rather than looked up because it is what the next marker
102    /// is compared against, and the comparison is per token.
103    name: String,
104    /// The line of that file the current output line stands for, presented rather than real,
105    /// since a marker is what tells the next compiler along where it is.
106    line: u32,
107    /// Whether anything has been written on the current output line.
108    printed: bool,
109    /// The include stack as the output has walked it, which is what decides whether a marker
110    /// says entering or returning. It is the output's own stack rather than the
111    /// preprocessor's, because by the time this runs the preprocessor's is long gone.
112    stack: Vec<FileId>,
113    /// The `#line` directives, in the order they were read.
114    lines: &'a [LineDirective],
115    /// How many of them have been written out.
116    next: usize,
117    /// Whether the current output line is a `#pragma` or another directive line.
118    directive: bool,
119}
120
121impl Printer<'_> {
122    /// Writes the marker for every `#line` that was read before the token at `at`.
123    ///
124    /// GCC prints one of these per directive, where the directive was written, and so does
125    /// this. Letting the effect show up on its own instead would put the same information in
126    /// the output in a different place: `#line 5` followed by three blank lines and a
127    /// statement comes out as a marker and three blank lines here, and as eight blank lines
128    /// if the printer only ever reacts to the line a token claims to be on.
129    fn line_directives(&mut self, at: usize) {
130        let (lines, sources) = (self.lines, self.sources);
131        while let Some(directive) = lines.get(self.next).filter(|d| d.at <= at) {
132            self.next += 1;
133            let Some(loc) = sources.presumed_after(directive.span.lo) else { continue };
134            self.end_line();
135            self.jump(loc.name, loc.line);
136        }
137    }
138
139    /// The marker that says which file the output starts in.
140    fn start(&mut self) {
141        if self.opts.line_markers {
142            self.out.push_str(&format!("# 1 {}\n", quoted(&self.name)));
143        }
144    }
145
146    /// Writes one token, with whatever whitespace has to come before it.
147    fn token(&mut self, tok: Tok, previous: Option<Tok>) {
148        let at = tok.report_span().lo;
149        // A token the preprocessor made up rather than read has no position to move to, so it
150        // stays on whatever line the output is already on. `_Pragma` produces these.
151        // Which file it is in is the real one, since that is what the include stack is kept
152        // in, and where it says it is is the presented one, since that is what a marker says.
153        // `sources` is copied out of `self` so that the borrow of the name outlives the call
154        // that needs `self` mutably. It is a shared reference the printer does not own.
155        // A directive is a line of its own, and so is whatever comes after one, even when both
156        // come from one source line. `_Pragma` makes that happen: a macro that expands to two of
157        // them puts both on the line it was used on, and the second `#` read back in the middle
158        // of the first line is a stray token rather than a pragma, which is how gcc sees it.
159        // What comes after a pragma is marked as starting a line when the pragma is made. The
160        // new line gets a marker, as in gcc, so the line numbers after it stay right.
161        let starts_line = tok.flags.has(TokenFlags::START_OF_LINE);
162        let hash = starts_line && tok.is(Punct::Hash);
163        let breaks = hash || (self.directive && starts_line);
164        if starts_line {
165            self.directive = hash;
166        }
167        let sources = self.sources;
168        if let Some(file) = sources.lookup_file(at) {
169            if let Some(loc) = sources.presumed(at) {
170                self.move_to(file, loc.name, loc.line, loc.column);
171                if breaks && self.printed {
172                    self.end_line();
173                    self.jump(loc.name, loc.line);
174                }
175            }
176        }
177        let text = spelling(tok, self.interner);
178        if self.space_before(tok, text, previous) {
179            self.out.push(' ');
180        }
181        self.out.push_str(text);
182        self.printed = true;
183    }
184
185    /// Whether a space goes between the previous token and this one.
186    ///
187    /// A run of spaces in the input is one space here, which is what GCC does. The indentation
188    /// of a line is the exception and it is rebuilt from the column instead, so the space this
189    /// returns for the first token of a line is the last of the ones `indent` wrote.
190    ///
191    /// The paste test is asked only where the two tokens did not arrive together. Two tokens
192    /// the user wrote next to each other read back as themselves by construction, because they
193    /// came out of the lexer that way, so `[52-2*sizeof(x)]` in a header prints as it was
194    /// written. It is a macro that can put two tokens next to each other that were never next
195    /// to each other, and that is where the question is worth asking. GCC arrives at the same
196    /// place from the other end: it inserts padding around each expansion and consults
197    /// `cpp_avoid_paste` only where one sits.
198    ///
199    /// "Arrived together" is the trace rather than the outermost invocation, because the
200    /// outermost is the same for every token of a nest and the boundaries inside it are real.
201    /// lz4 writes `#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2)` over a `LZ4_MEMORY_USAGE` of 14,
202    /// and the `14` and the `-` are two steps apart, so gcc prints `(14 -2)` and so does this.
203    fn space_before(&self, tok: Tok, text: &str, previous: Option<Tok>) -> bool {
204        if tok.flags.has(TokenFlags::LEADING_SPACE) {
205            return true;
206        }
207        match previous {
208            Some(prev) if self.printed && prev.trace != tok.trace => {
209                avoid_paste(prev, spelling(prev, self.interner), tok, text)
210            }
211            _ => false,
212        }
213    }
214
215    /// Moves the output to a file and a line, printing whatever that takes.
216    fn move_to(&mut self, file: FileId, name: &str, line: u32, column: u32) {
217        if file == self.file && line == self.line && self.printed && name == self.name {
218            return;
219        }
220        self.end_line();
221        if file != self.file {
222            self.marker(file, name, line);
223        } else if name != self.name {
224            // Same file, different name, which is a `#line` that renamed it. GCC prints that
225            // as a plain marker with no flag on it, the same as a jump within a file, because
226            // as far as the output is concerned that is what it is.
227            self.jump(name, line);
228        } else if line > self.line && line - self.line <= MAX_BLANKS {
229            // Close enough to walk to. Under `-P` the walk is skipped and the lines simply
230            // follow each other, which is what makes `-P` output compact.
231            if self.opts.line_markers {
232                for _ in self.line..line {
233                    self.out.push('\n');
234                }
235            }
236            self.line = line;
237        } else if line != self.line {
238            // Too far to walk, or backwards, which happens when a macro invocation spans lines
239            // and the tokens after it are reported at the line it started on.
240            self.jump(name, line);
241        }
242        self.indent(column);
243    }
244
245    /// Ends the current output line, if anything is on it.
246    fn end_line(&mut self) {
247        if self.printed {
248            self.out.push('\n');
249            self.line += 1;
250            self.printed = false;
251        }
252    }
253
254    /// A marker that says the output has changed file.
255    fn marker(&mut self, file: FileId, name: &str, line: u32) {
256        // Entering or returning is decided by whether the file is already on the stack. A file
257        // that is not is one the output has not been in, which is an entry however it was
258        // reached.
259        let flag = match self.stack.iter().position(|&f| f == file) {
260            Some(at) => {
261                self.stack.truncate(at + 1);
262                2
263            }
264            None => {
265                self.stack.push(file);
266                1
267            }
268        };
269        if self.opts.line_markers {
270            self.out.push_str(&format!("# {line} {} {flag}\n", quoted(name)));
271        }
272        self.file = file;
273        self.set_name(name);
274        self.line = line;
275    }
276
277    /// A marker that says the output has moved within the same file.
278    fn jump(&mut self, name: &str, line: u32) {
279        if self.opts.line_markers {
280            self.out.push_str(&format!("# {line} {}\n", quoted(name)));
281        }
282        self.set_name(name);
283        self.line = line;
284    }
285
286    /// Records the name the output is now going under, without allocating when it has not
287    /// changed, which is every token of every file that has no `#line` in it.
288    fn set_name(&mut self, name: &str) {
289        if self.name != name {
290            self.name.clear();
291            self.name.push_str(name);
292        }
293    }
294
295    /// Indents the first token of a line to the column it was written at.
296    ///
297    /// One space short of the column, because the token's own leading space flag supplies the
298    /// last one. GCC does exactly this, and the reason to copy it rather than to print the
299    /// tokens flush left is that indentation is most of what makes preprocessed output
300    /// readable when something has gone wrong in it.
301    fn indent(&mut self, column: u32) {
302        if self.printed {
303            return;
304        }
305        for _ in 2..column {
306            self.out.push(' ');
307        }
308    }
309
310    /// The finished text, which always ends in a newline.
311    fn finish(mut self) -> String {
312        if self.printed {
313            self.out.push('\n');
314        }
315        self.out
316    }
317}
318
319/// Whether writing these two tokens next to each other would change what they say.
320///
321/// This is GCC's `cpp_avoid_paste` with the same answers, written over spellings rather than
322/// over token codes. The word case is deliberately wider than GCC's: an identifier followed by
323/// a number gets a space here, because `x` and `1` written together are the single identifier
324/// `x1`, and output that does not read back as itself is not output.
325fn avoid_paste(prev: Tok, prev_text: &str, next: Tok, next_text: &str) -> bool {
326    let Some(first) = next_text.chars().next() else {
327        return false;
328    };
329    // Anything that ends in a word character followed by anything that starts as one. This
330    // covers name and name, name and number, number and number, and the prefixed forms of a
331    // character constant and a string literal, which are a name followed by a quote.
332    let word = matches!(prev.kind, PpTokenKind::Ident | PpTokenKind::Number | PpTokenKind::Other);
333    if word {
334        let joins = matches!(
335            next.kind,
336            PpTokenKind::Ident
337                | PpTokenKind::Number
338                | PpTokenKind::CharConst
339                | PpTokenKind::StringLit
340        );
341        if joins {
342            return true;
343        }
344        // A pp-number swallows a following sign after an exponent, and a `.` either side of
345        // one is part of the number rather than a separate token.
346        if prev.kind == PpTokenKind::Number {
347            return matches!(first, '.' | '+' | '-');
348        }
349        return false;
350    }
351
352    // An `=` glues onto every operator that has a compound assignment form, and onto the
353    // comparisons, which is most of them, so it is asked first.
354    if first == '=' {
355        return matches!(
356            prev_text,
357            "=" | "!" | "<" | ">" | "+" | "-" | "*" | "/" | "%" | "&" | "|" | "^" | "<<" | ">>"
358        );
359    }
360    match prev_text {
361        ">" => first == '>',
362        "<" => matches!(first, '<' | '%' | ':'),
363        "+" => first == '+',
364        "-" => matches!(first, '-' | '>'),
365        // Not an operator that pastes: `/` and `*` written together open a comment, and `//`
366        // swallows the rest of the line.
367        "/" => matches!(first, '/' | '*'),
368        "%" => matches!(first, ':' | '%' | '>'),
369        "&" => first == '&',
370        "|" => first == '|',
371        ":" => matches!(first, ':' | '>'),
372        "." => first == '.' || next.kind == PpTokenKind::Number,
373        "#" => matches!(first, '#' | '%'),
374        _ => false,
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use rucc_diag::SourceMap;
381    use rucc_session::{MemoryFileSystem, SearchPath};
382
383    use super::*;
384    use crate::directive::Preprocessor;
385    use crate::include::Context;
386
387    /// A translation unit through phase 4 and back out as text.
388    struct Run {
389        interner: Interner,
390        sources: SourceMap,
391        fs: MemoryFileSystem,
392        search: SearchPath,
393        pp: Preprocessor,
394    }
395
396    impl Run {
397        fn new() -> Run {
398            Run {
399                interner: Interner::new(),
400                sources: SourceMap::new(),
401                fs: MemoryFileSystem::new(),
402                search: SearchPath::new(),
403                pp: Preprocessor::new(),
404            }
405        }
406
407        fn file(&mut self, path: &str, contents: &str) {
408            self.fs.insert(path, contents.as_bytes().to_vec());
409        }
410
411        fn go(&mut self, src: &str) -> String {
412            self.print(src, PrintOptions::new())
413        }
414
415        fn print(&mut self, src: &str, opts: PrintOptions) -> String {
416            let main =
417                self.sources.add("/main.c", src.as_bytes().to_vec()).expect("the map has room");
418            let out = {
419                let mut cx =
420                    Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
421                self.pp.run(main, &mut cx)
422            };
423            assert!(self.pp.diagnostics().is_empty(), "{:?}", self.pp.diagnostics());
424            print(main, &out, self.pp.line_directives(), &self.sources, &self.interner, opts)
425        }
426    }
427
428    #[test]
429    fn the_first_line_says_which_file_this_is() {
430        let mut run = Run::new();
431        assert_eq!(run.go("int x;\n"), "# 1 \"/main.c\"\nint x;\n");
432    }
433
434    #[test]
435    fn two_pragmas_from_one_macro_are_two_lines_and_what_follows_is_a_third() {
436        let mut run = Run::new();
437        let src = "#define P _Pragma(\"a\") _Pragma(\"b\")\nP int x;\nint y;\n";
438        assert_eq!(
439            run.go(src),
440            "# 1 \"/main.c\"\n\n#pragma a\n# 2 \"/main.c\"\n#pragma b\n# 2 \"/main.c\"\n int x;\nint y;\n"
441        );
442        assert_eq!(
443            run.print(src, PrintOptions { line_markers: false }),
444            "#pragma a\n#pragma b\n int x;\nint y;\n"
445        );
446    }
447
448    #[test]
449    fn a_line_the_preprocessor_ate_comes_back_as_a_blank_one() {
450        let mut run = Run::new();
451        // The definition produced no tokens, so line 2 is blank and `x` is still on line 3.
452        // Keeping it there is what lets a diagnostic from a later phase name the right line.
453        assert_eq!(run.go("#define N 1\nint x;\n"), "# 1 \"/main.c\"\n\nint x;\n");
454    }
455
456    #[test]
457    fn a_long_gap_is_a_marker_rather_than_a_page_of_blank_lines() {
458        let mut run = Run::new();
459        let src = format!("a;{}b;\n", "\n".repeat(20));
460        let text = run.go(&src);
461        assert!(text.contains("# 21 \"/main.c\"\nb;\n"), "{text}");
462        assert!(!text.contains("\n\n\n"), "a gap that big is a marker, not blank lines: {text}");
463    }
464
465    #[test]
466    fn entering_and_leaving_a_header_are_both_marked() {
467        let mut run = Run::new();
468        run.file("/one.h", "int from_the_header;\n");
469        let text = run.go("#include \"one.h\"\nint after;\n");
470        assert_eq!(
471            text,
472            "# 1 \"/main.c\"\n\
473             # 1 \"/one.h\" 1\n\
474             int from_the_header;\n\
475             # 2 \"/main.c\" 2\n\
476             int after;\n"
477        );
478    }
479
480    #[test]
481    fn dash_p_prints_the_tokens_and_nothing_else() {
482        let mut run = Run::new();
483        run.file("/one.h", "int from_the_header;\n");
484        let src = "#include \"one.h\"\n\n\n\nint after;\n";
485        let text = run.print(src, PrintOptions { line_markers: false });
486        assert_eq!(text, "int from_the_header;\nint after;\n");
487    }
488
489    #[test]
490    fn indentation_survives() {
491        let mut run = Run::new();
492        assert_eq!(run.go("    int x;\n"), "# 1 \"/main.c\"\n    int x;\n");
493    }
494
495    #[test]
496    fn a_space_goes_in_where_the_tokens_would_otherwise_paste() {
497        let mut run = Run::new();
498        // `+ +` rather than `++`, and `- -` rather than `--`, because those are different
499        // operators and the output has to say what the input said.
500        let src = "#define P +\n#define M -\nP+x;\nM-x;\n";
501        assert_eq!(run.go(src), "# 1 \"/main.c\"\n\n\n+ +x;\n- -x;\n");
502    }
503
504    #[test]
505    fn a_name_and_a_number_do_not_run_together() {
506        let mut run = Run::new();
507        // `x1` would read back as one identifier, so the space is not optional.
508        assert_eq!(run.go("#define J(a,b) a b\nJ(x,1)J(2,y)\n"), "# 1 \"/main.c\"\n\nx 1 2 y\n");
509    }
510
511    /// The paste test is for tokens a macro put next to each other. Two the user wrote next to
512    /// each other came out of the lexer that way and read back as themselves, so nothing is
513    /// inserted between them: the kernel's `sound/asound.h` writes an array bound as
514    /// `[52-2*sizeof(x)]` and GCC prints it back unchanged.
515    #[test]
516    fn a_paste_is_only_avoided_where_a_macro_put_the_tokens_together() {
517        let mut run = Run::new();
518        assert_eq!(
519            run.go("char a[52-2*sizeof(int)];\n"),
520            "# 1 \"/main.c\"\nchar a[52-2*sizeof(int)];\n"
521        );
522
523        // The number comes out of `N` and the sign does not, so they did not arrive together
524        // and `52-2` would read back as a different pp-number than the two tokens it is.
525        let mut run = Run::new();
526        assert_eq!(run.go("#define N 52\nN-2;\n"), "# 1 \"/main.c\"\n\n52 -2;\n");
527
528        // Both out of the same expansion, so the body's own spacing is what is printed.
529        let mut run = Run::new();
530        assert_eq!(run.go("#define S 41+1\nS;\n"), "# 1 \"/main.c\"\n\n41+1;\n");
531
532        // A nest, which is lz4's `#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2)` cut down. The two
533        // tokens share an outermost invocation and are still a step apart, and gcc prints the
534        // space, so the question is asked of the trace rather than of the outermost.
535        let mut run = Run::new();
536        assert_eq!(
537            run.go("#define A 14\n#define B (A-2)\nint t[1 << B];\n"),
538            "# 1 \"/main.c\"\n\n\nint t[1 << (14 -2)];\n"
539        );
540    }
541
542    #[test]
543    fn a_slash_and_a_star_do_not_open_a_comment() {
544        let mut run = Run::new();
545        assert_eq!(run.go("#define D /\nD*p;\n"), "# 1 \"/main.c\"\n\n/ *p;\n");
546    }
547
548    #[test]
549    fn a_run_of_spaces_is_one_space_and_the_indent_is_the_real_one() {
550        let mut run = Run::new();
551        // GCC collapses whitespace between tokens to one space and rebuilds the indentation
552        // from the column, so a line that was indented by two still is.
553        assert_eq!(run.go("  int   x = a+b;\n"), "# 1 \"/main.c\"\n  int x = a+b;\n");
554    }
555
556    #[test]
557    fn a_macro_that_spans_lines_leaves_the_output_where_the_call_was() {
558        let mut run = Run::new();
559        let text = run.go("#define ADD(a, b) a + b\nADD(1,\n    2)\nlast;\n");
560        assert_eq!(text, "# 1 \"/main.c\"\n\n1 + 2\n\nlast;\n");
561    }
562
563    #[test]
564    fn a_macro_that_expands_to_nothing_leaves_its_space_behind() {
565        let mut run = Run::new();
566        // GCC and clang both print `int a ;` here, and the space is not decoration. The glibc
567        // headers hang `__THROW` and its relatives off the end of several hundred prototypes
568        // per file, and on a dialect where those expand to nothing this one space is the whole
569        // difference between agreeing with the reference compiler and not.
570        let text = run.print("#define E\nint a E;\n", PrintOptions { line_markers: false });
571        assert_eq!(text, "int a ;\n");
572    }
573
574    #[test]
575    fn the_space_is_only_left_where_there_was_one() {
576        let mut run = Run::new();
577        // No space before the macro means no space after it. `a1(E);` is `a1();` and not
578        // `a1( );`, which is the case that stops this rule from turning into "always insert".
579        let text = run.print("#define E\na1(E);\n", PrintOptions { line_markers: false });
580        assert_eq!(text, "a1();\n");
581    }
582
583    #[test]
584    fn a_space_owed_by_one_empty_macro_is_not_paid_twice() {
585        let mut run = Run::new();
586        // Three vanishing macros in a row owe one space between them, not three. The debt is
587        // handed along until a token that survives takes it.
588        let text = run.print("#define E\nd1 E E E d2;\n", PrintOptions { line_markers: false });
589        assert_eq!(text, "d1 d2;\n");
590    }
591
592    #[test]
593    fn the_space_crosses_out_of_the_expansion_that_owed_it() {
594        let mut run = Run::new();
595        // `J(4)` expands to `4 E`, and the `E` vanishes at the end of the replacement list. The
596        // token that takes the space is the `;` from the source, which the expansion never saw.
597        let text = run
598            .print("#define E\n#define J(x) x E\np6 J(4);\n", PrintOptions { line_markers: false });
599        assert_eq!(text, "p6 4 ;\n");
600    }
601
602    #[test]
603    fn a_function_like_macro_with_an_empty_body_leaves_a_space_too() {
604        let mut run = Run::new();
605        // The rule is about the invocation vanishing, not about which kind of macro it was.
606        let text = run
607            .print("#define F(x)\nint d(int F(9), int);\n", PrintOptions { line_markers: false });
608        assert_eq!(text, "int d(int , int);\n");
609    }
610
611    #[test]
612    fn a_line_directive_is_a_marker_where_it_was_written() {
613        let mut run = Run::new();
614        // GCC writes the marker at the directive and then walks the three blank lines from
615        // there. Reacting to the line the statement claims to be on instead would put the same
616        // information in the output as eight blank lines and no marker.
617        let text = run.go("#line 5\n\n\n\nint a;\n");
618        assert_eq!(text, "# 1 \"/main.c\"\n# 5 \"/main.c\"\n\n\n\nint a;\n");
619    }
620
621    #[test]
622    fn two_directives_in_a_row_are_two_markers() {
623        let mut run = Run::new();
624        assert_eq!(
625            run.go("#line 5\n#line 9\nint a;\n"),
626            "# 1 \"/main.c\"\n# 5 \"/main.c\"\n# 9 \"/main.c\"\nint a;\n"
627        );
628    }
629
630    #[test]
631    fn a_directive_with_nothing_after_it_still_writes_its_marker() {
632        let mut run = Run::new();
633        assert_eq!(run.go("int a;\n#line 5\n"), "# 1 \"/main.c\"\nint a;\n# 5 \"/main.c\"\n");
634    }
635
636    #[test]
637    fn the_marker_goes_where_the_directive_is_and_not_where_its_bytes_are() {
638        let mut run = Run::new();
639        // The header is added to the source map after the file that includes it, so its bytes
640        // come after every byte of this file, including the ones after the `#include`. A
641        // printer that ordered the markers by position would write the rename after the
642        // header rather than before it.
643        run.file("/one.h", "int in_header;\n");
644        let text = run.go("#line 900 \"outer\"\n#include \"one.h\"\nint after;\n");
645        assert_eq!(
646            text,
647            "# 1 \"/main.c\"\n\
648             # 900 \"outer\"\n\
649             # 1 \"/one.h\" 1\n\
650             int in_header;\n\
651             # 901 \"outer\" 2\n\
652             int after;\n"
653        );
654    }
655
656    #[test]
657    fn dash_p_drops_the_markers_a_directive_makes_like_every_other_one() {
658        let mut run = Run::new();
659        let text = run.print("#line 900 \"outer\"\nint a;\n", PrintOptions { line_markers: false });
660        assert_eq!(text, "int a;\n");
661    }
662}