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