1use rucc_base::Interner;
17use rucc_diag::{FileId, SourceMap};
18use rucc_lex::{PpTokenKind, TokenFlags};
19
20use crate::include::{quoted, spelling};
21use crate::token::Tok;
22
23const MAX_BLANKS: u32 = 8;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct PrintOptions {
32 pub line_markers: bool,
37}
38
39impl PrintOptions {
40 pub fn new() -> PrintOptions {
42 PrintOptions { line_markers: true }
43 }
44}
45
46impl Default for PrintOptions {
47 fn default() -> PrintOptions {
48 PrintOptions::new()
49 }
50}
51
52pub fn print(
57 main: FileId,
58 tokens: &[Tok],
59 sources: &SourceMap,
60 interner: &Interner,
61 opts: PrintOptions,
62) -> String {
63 let mut printer = Printer {
64 out: String::new(),
65 opts,
66 sources,
67 interner,
68 file: main,
69 line: 1,
70 printed: false,
71 stack: vec![main],
72 };
73 printer.start();
74 let mut previous: Option<Tok> = None;
75 for &tok in tokens {
76 printer.token(tok, previous);
77 previous = Some(tok);
78 }
79 printer.finish()
80}
81
82struct Printer<'a> {
84 out: String,
85 opts: PrintOptions,
86 sources: &'a SourceMap,
87 interner: &'a Interner,
88 file: FileId,
90 line: u32,
92 printed: bool,
94 stack: Vec<FileId>,
98}
99
100impl Printer<'_> {
101 fn start(&mut self) {
103 if self.opts.line_markers {
104 self.out.push_str(&format!("# 1 {}\n", quoted(&self.sources.file(self.file).name)));
105 }
106 }
107
108 fn token(&mut self, tok: Tok, previous: Option<Tok>) {
110 let at = tok.report_span().lo;
111 if let Some(loc) = self.sources.lookup(at) {
114 self.move_to(loc.file, loc.line, loc.column);
115 }
116 let text = spelling(tok, self.interner);
117 if self.space_before(tok, text, previous) {
118 self.out.push(' ');
119 }
120 self.out.push_str(text);
121 self.printed = true;
122 }
123
124 fn space_before(&self, tok: Tok, text: &str, previous: Option<Tok>) -> bool {
130 if tok.flags.has(TokenFlags::LEADING_SPACE) {
131 return true;
132 }
133 match previous {
134 Some(prev) if self.printed => {
135 avoid_paste(prev, spelling(prev, self.interner), tok, text)
136 }
137 _ => false,
138 }
139 }
140
141 fn move_to(&mut self, file: FileId, line: u32, column: u32) {
143 if file == self.file && line == self.line && self.printed {
144 return;
145 }
146 self.end_line();
147 if file != self.file {
148 self.marker(file, line);
149 } else if line > self.line && line - self.line <= MAX_BLANKS {
150 if self.opts.line_markers {
153 for _ in self.line..line {
154 self.out.push('\n');
155 }
156 }
157 self.line = line;
158 } else if line != self.line {
159 self.jump(file, line);
162 }
163 self.indent(column);
164 }
165
166 fn end_line(&mut self) {
168 if self.printed {
169 self.out.push('\n');
170 self.line += 1;
171 self.printed = false;
172 }
173 }
174
175 fn marker(&mut self, file: FileId, line: u32) {
177 let flag = match self.stack.iter().position(|&f| f == file) {
181 Some(at) => {
182 self.stack.truncate(at + 1);
183 2
184 }
185 None => {
186 self.stack.push(file);
187 1
188 }
189 };
190 if self.opts.line_markers {
191 let name = quoted(&self.sources.file(file).name);
192 self.out.push_str(&format!("# {line} {name} {flag}\n"));
193 }
194 self.file = file;
195 self.line = line;
196 }
197
198 fn jump(&mut self, file: FileId, line: u32) {
200 if self.opts.line_markers {
201 let name = quoted(&self.sources.file(file).name);
202 self.out.push_str(&format!("# {line} {name}\n"));
203 }
204 self.line = line;
205 }
206
207 fn indent(&mut self, column: u32) {
214 if self.printed {
215 return;
216 }
217 for _ in 2..column {
218 self.out.push(' ');
219 }
220 }
221
222 fn finish(mut self) -> String {
224 if self.printed {
225 self.out.push('\n');
226 }
227 self.out
228 }
229}
230
231fn avoid_paste(prev: Tok, prev_text: &str, next: Tok, next_text: &str) -> bool {
238 let Some(first) = next_text.chars().next() else {
239 return false;
240 };
241 let word = matches!(prev.kind, PpTokenKind::Ident | PpTokenKind::Number | PpTokenKind::Other);
245 if word {
246 let joins = matches!(
247 next.kind,
248 PpTokenKind::Ident
249 | PpTokenKind::Number
250 | PpTokenKind::CharConst
251 | PpTokenKind::StringLit
252 );
253 if joins {
254 return true;
255 }
256 if prev.kind == PpTokenKind::Number {
259 return matches!(first, '.' | '+' | '-');
260 }
261 return false;
262 }
263
264 if first == '=' {
267 return matches!(
268 prev_text,
269 "=" | "!" | "<" | ">" | "+" | "-" | "*" | "/" | "%" | "&" | "|" | "^" | "<<" | ">>"
270 );
271 }
272 match prev_text {
273 ">" => first == '>',
274 "<" => matches!(first, '<' | '%' | ':'),
275 "+" => first == '+',
276 "-" => matches!(first, '-' | '>'),
277 "/" => matches!(first, '/' | '*'),
280 "%" => matches!(first, ':' | '%' | '>'),
281 "&" => first == '&',
282 "|" => first == '|',
283 ":" => matches!(first, ':' | '>'),
284 "." => first == '.' || next.kind == PpTokenKind::Number,
285 "#" => matches!(first, '#' | '%'),
286 _ => false,
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use rucc_diag::SourceMap;
293 use rucc_session::{MemoryFileSystem, SearchPath};
294
295 use super::*;
296 use crate::directive::Preprocessor;
297 use crate::include::Context;
298
299 struct Run {
301 interner: Interner,
302 sources: SourceMap,
303 fs: MemoryFileSystem,
304 search: SearchPath,
305 pp: Preprocessor,
306 }
307
308 impl Run {
309 fn new() -> Run {
310 Run {
311 interner: Interner::new(),
312 sources: SourceMap::new(),
313 fs: MemoryFileSystem::new(),
314 search: SearchPath::new(),
315 pp: Preprocessor::new(),
316 }
317 }
318
319 fn file(&mut self, path: &str, contents: &str) {
320 self.fs.insert(path, contents.as_bytes().to_vec());
321 }
322
323 fn go(&mut self, src: &str) -> String {
324 self.print(src, PrintOptions::new())
325 }
326
327 fn print(&mut self, src: &str, opts: PrintOptions) -> String {
328 let main =
329 self.sources.add("/main.c", src.as_bytes().to_vec()).expect("the map has room");
330 let out = {
331 let mut cx =
332 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
333 self.pp.run(main, &mut cx)
334 };
335 assert!(self.pp.diagnostics().is_empty(), "{:?}", self.pp.diagnostics());
336 print(main, &out, &self.sources, &self.interner, opts)
337 }
338 }
339
340 #[test]
341 fn the_first_line_says_which_file_this_is() {
342 let mut run = Run::new();
343 assert_eq!(run.go("int x;\n"), "# 1 \"/main.c\"\nint x;\n");
344 }
345
346 #[test]
347 fn a_line_the_preprocessor_ate_comes_back_as_a_blank_one() {
348 let mut run = Run::new();
349 assert_eq!(run.go("#define N 1\nint x;\n"), "# 1 \"/main.c\"\n\nint x;\n");
352 }
353
354 #[test]
355 fn a_long_gap_is_a_marker_rather_than_a_page_of_blank_lines() {
356 let mut run = Run::new();
357 let src = format!("a;{}b;\n", "\n".repeat(20));
358 let text = run.go(&src);
359 assert!(text.contains("# 21 \"/main.c\"\nb;\n"), "{text}");
360 assert!(!text.contains("\n\n\n"), "a gap that big is a marker, not blank lines: {text}");
361 }
362
363 #[test]
364 fn entering_and_leaving_a_header_are_both_marked() {
365 let mut run = Run::new();
366 run.file("/one.h", "int from_the_header;\n");
367 let text = run.go("#include \"one.h\"\nint after;\n");
368 assert_eq!(
369 text,
370 "# 1 \"/main.c\"\n\
371 # 1 \"/one.h\" 1\n\
372 int from_the_header;\n\
373 # 2 \"/main.c\" 2\n\
374 int after;\n"
375 );
376 }
377
378 #[test]
379 fn dash_p_prints_the_tokens_and_nothing_else() {
380 let mut run = Run::new();
381 run.file("/one.h", "int from_the_header;\n");
382 let src = "#include \"one.h\"\n\n\n\nint after;\n";
383 let text = run.print(src, PrintOptions { line_markers: false });
384 assert_eq!(text, "int from_the_header;\nint after;\n");
385 }
386
387 #[test]
388 fn indentation_survives() {
389 let mut run = Run::new();
390 assert_eq!(run.go(" int x;\n"), "# 1 \"/main.c\"\n int x;\n");
391 }
392
393 #[test]
394 fn a_space_goes_in_where_the_tokens_would_otherwise_paste() {
395 let mut run = Run::new();
396 let src = "#define P +\n#define M -\nP+x;\nM-x;\n";
399 assert_eq!(run.go(src), "# 1 \"/main.c\"\n\n\n+ +x;\n- -x;\n");
400 }
401
402 #[test]
403 fn a_name_and_a_number_do_not_run_together() {
404 let mut run = Run::new();
405 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");
407 }
408
409 #[test]
410 fn a_slash_and_a_star_do_not_open_a_comment() {
411 let mut run = Run::new();
412 assert_eq!(run.go("#define D /\nD*p;\n"), "# 1 \"/main.c\"\n\n/ *p;\n");
413 }
414
415 #[test]
416 fn a_run_of_spaces_is_one_space_and_the_indent_is_the_real_one() {
417 let mut run = Run::new();
418 assert_eq!(run.go(" int x = a+b;\n"), "# 1 \"/main.c\"\n int x = a+b;\n");
421 }
422
423 #[test]
424 fn a_macro_that_spans_lines_leaves_the_output_where_the_call_was() {
425 let mut run = Run::new();
426 let text = run.go("#define ADD(a, b) a + b\nADD(1,\n 2)\nlast;\n");
427 assert_eq!(text, "# 1 \"/main.c\"\n\n1 + 2\n\nlast;\n");
428 }
429
430 #[test]
431 fn a_macro_that_expands_to_nothing_leaves_its_space_behind() {
432 let mut run = Run::new();
433 let text = run.print("#define E\nint a E;\n", PrintOptions { line_markers: false });
438 assert_eq!(text, "int a ;\n");
439 }
440
441 #[test]
442 fn the_space_is_only_left_where_there_was_one() {
443 let mut run = Run::new();
444 let text = run.print("#define E\na1(E);\n", PrintOptions { line_markers: false });
447 assert_eq!(text, "a1();\n");
448 }
449
450 #[test]
451 fn a_space_owed_by_one_empty_macro_is_not_paid_twice() {
452 let mut run = Run::new();
453 let text = run.print("#define E\nd1 E E E d2;\n", PrintOptions { line_markers: false });
456 assert_eq!(text, "d1 d2;\n");
457 }
458
459 #[test]
460 fn the_space_crosses_out_of_the_expansion_that_owed_it() {
461 let mut run = Run::new();
462 let text = run
465 .print("#define E\n#define J(x) x E\np6 J(4);\n", PrintOptions { line_markers: false });
466 assert_eq!(text, "p6 4 ;\n");
467 }
468
469 #[test]
470 fn a_function_like_macro_with_an_empty_body_leaves_a_space_too() {
471 let mut run = Run::new();
472 let text = run
474 .print("#define F(x)\nint d(int F(9), int);\n", PrintOptions { line_markers: false });
475 assert_eq!(text, "int d(int , int);\n");
476 }
477}