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}