1use std::collections::HashMap;
18use std::path::{Path, PathBuf};
19
20use rucc_base::{Interner, Symbol};
21use rucc_diag::{Diagnostic, FileId, SourceMapFull, Span};
22use rucc_gnu::Kind;
23use rucc_lex::{Options, PpToken, PpTokenKind, Punct, TokenFlags, tokenize};
24use rucc_session::{Found, IncludeForm};
25use rucc_target::TargetInfo;
26
27use crate::cond;
28use crate::embed;
29use crate::expand::Expander;
30use crate::include::{
31 Context, Frame, Header, Reader, directory_of, header_from_token, header_from_tokens, spelling,
32};
33use crate::macros::{Builtin, MacroTable, parse_define};
34use crate::predef::{BUILT_IN, COMMAND_LINE, Predef, built_in, command_line};
35use crate::token::Tok;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39enum Guard {
40 Once,
42 Macro(Symbol),
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51enum Scan {
52 Start,
54 Inside(Symbol),
56 Closed(Symbol),
58 No,
60}
61
62#[derive(Debug)]
64struct Cond {
65 span: Span,
67 live: bool,
70 taken: bool,
73 enclosing_live: bool,
75 seen_else: bool,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct LineDirective {
82 pub span: Span,
84 pub line: u32,
86 pub file: Option<Symbol>,
88 pub at: usize,
97}
98
99#[derive(Debug, Default)]
104pub struct Preprocessor {
105 macros: MacroTable,
106 expander: Expander,
107 diagnostics: Vec<Diagnostic>,
108 conds: Vec<Cond>,
109 lines: Vec<LineDirective>,
110 stack: Vec<Frame>,
112 markers: Vec<String>,
116 seen: HashMap<PathBuf, Guard>,
120}
121
122impl Preprocessor {
123 pub fn new() -> Preprocessor {
125 Preprocessor::default()
126 }
127
128 pub fn macros(&self) -> &MacroTable {
130 &self.macros
131 }
132
133 pub fn macros_mut(&mut self) -> &mut MacroTable {
135 &mut self.macros
136 }
137
138 pub fn diagnostics(&self) -> &[Diagnostic] {
140 &self.diagnostics
141 }
142
143 pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
145 std::mem::take(&mut self.diagnostics)
146 }
147
148 pub fn line_directives(&self) -> &[LineDirective] {
154 &self.lines
155 }
156
157 pub fn predefine(
169 &mut self,
170 target: &TargetInfo,
171 opts: &Predef,
172 cx: &mut Context<'_>,
173 ) -> Result<(), SourceMapFull> {
174 let names = Names::new(cx.interner);
175 let file = self.synthetic(BUILT_IN, built_in(target, opts), cx, &names)?;
176 let start = cx.sources.file(file).start;
182 for (spelling, builtin) in Builtin::ALL {
183 let name = cx.interner.intern(spelling);
184 self.macros.define_builtin(name, builtin, Span::new(start, start));
185 }
186 let text = command_line(opts);
187 if !text.is_empty() {
188 self.synthetic(COMMAND_LINE, text, cx, &names)?;
189 }
190 Ok(())
191 }
192
193 fn synthetic(
195 &mut self,
196 name: &str,
197 text: String,
198 cx: &mut Context<'_>,
199 names: &Names,
200 ) -> Result<FileId, SourceMapFull> {
201 let file = cx.sources.add(name, text.into_bytes())?;
202 let mut out = Vec::new();
203 let path = PathBuf::from(name);
207 let id = cx.fs.identity(&path);
208 self.stack.push(Frame { at: Span::DUMMY, path, id, dir: None, next: 0 });
209 self.process(file, &mut out, cx, names);
210 self.stack.clear();
211 debug_assert!(out.is_empty(), "{name} is directives only and produces no tokens");
212 Ok(file)
213 }
214
215 pub fn run(&mut self, file: FileId, cx: &mut Context<'_>) -> Vec<Tok> {
220 let names = Names::new(cx.interner);
221 let mut out = Vec::new();
222 let name = cx.sources.file(file).name.clone();
223 let dir = directory_of(&name);
224 let path = PathBuf::from(name);
227 let id = cx.fs.identity(&path);
228 self.stack.push(Frame { at: Span::DUMMY, path, id, dir, next: 0 });
229 self.process(file, &mut out, cx, &names);
230 self.stack.clear();
231 out
232 }
233
234 fn process(&mut self, file: FileId, out: &mut Vec<Tok>, cx: &mut Context<'_>, names: &Names) {
236 let bytes = cx.sources.file(file).shared_bytes();
239 let start = cx.sources.file(file).start;
240 let mut reader = Reader::new(bytes.as_slice(), start, cx.lex);
241 let depth_on_entry = self.conds.len();
242 let mut text: Vec<Tok> = Vec::new();
246 let mut body: Vec<PpToken> = Vec::new();
247 let mut scan = Scan::Start;
248
249 loop {
250 let was_live = self.live();
251 let first = reader.next(cx.interner);
252 if first.is_eof() {
253 break;
254 }
255 if is_directive(first) {
256 self.flush(&mut text, out, cx, names);
257 body.clear();
258 let name_tok = reader.next(cx.interner);
259 if name_tok.is_eof() || name_tok.flags.has(TokenFlags::START_OF_LINE) {
262 reader.put_back(name_tok);
263 continue;
264 }
265 body.push(name_tok);
266 if was_live && is_include(ident_of(&name_tok), names) {
271 if let Some(header) = reader.header_name(cx.interner) {
272 body.push(header);
273 }
274 }
275 reader.line(cx.interner, &mut body);
276 let opens =
277 matches!(scan, Scan::Start).then(|| guard_opener(&body, names)).flatten();
278 self.directive(&body, first.span, out, cx, names);
279 scan = match scan {
280 Scan::Start => match opens {
284 Some(name) if self.conds.len() == depth_on_entry + 1 => Scan::Inside(name),
285 _ => Scan::No,
286 },
287 Scan::Inside(name) if self.conds.len() == depth_on_entry => Scan::Closed(name),
288 Scan::Inside(name) => Scan::Inside(name),
289 Scan::Closed(_) | Scan::No => Scan::No,
290 };
291 } else {
292 body.clear();
293 reader.line(cx.interner, &mut body);
294 if self.live() {
295 let operator = ident_of(&first) == Some(names.pragma_op)
301 || body.iter().any(|t| ident_of(t) == Some(names.pragma_op));
302 if operator {
303 self.flush(&mut text, out, cx, names);
304 }
305 text.push(Tok::new(first));
306 text.extend(body.iter().copied().map(Tok::new));
307 if operator {
308 self.flush(&mut text, out, cx, names);
309 }
310 }
311 if !matches!(scan, Scan::Inside(_)) {
313 scan = Scan::No;
314 }
315 }
316 let complaints = reader.take_diagnostics();
319 if was_live || self.live() {
320 self.diagnostics.extend(complaints);
321 }
322 }
323 self.flush(&mut text, out, cx, names);
324 self.diagnostics.extend(reader.take_diagnostics());
325
326 if let Scan::Closed(name) = scan {
329 if self.macros.is_defined(name) {
330 if let Some(frame) = self.stack.last() {
331 self.seen.entry(frame.id.clone()).or_insert(Guard::Macro(name));
332 }
333 }
334 }
335
336 for cond in self.conds.drain(depth_on_entry..) {
339 self.diagnostics
340 .push(Diagnostic::error("unterminated `#if`", cond.span).with_code("E0330"));
341 }
342 }
343
344 fn live(&self) -> bool {
346 self.conds.last().is_none_or(|c| c.live)
347 }
348
349 fn flush(
351 &mut self,
352 text: &mut Vec<Tok>,
353 out: &mut Vec<Tok>,
354 cx: &mut Context<'_>,
355 names: &Names,
356 ) {
357 if text.is_empty() {
358 return;
359 }
360 let taken = std::mem::take(text);
361 let expanded = self.expander.expand_toks(taken, &self.macros, cx.interner, cx.sources);
362 self.diagnostics.append(&mut self.expander.take_diagnostics());
363 let expanded = self.resolve_has(expanded, cx, names, Pass::Text);
368 self.pragma_operator(expanded, out, cx.interner, names);
369 }
370
371 fn directive(
373 &mut self,
374 body: &[PpToken],
375 hash: Span,
376 out: &mut Vec<Tok>,
377 cx: &mut Context<'_>,
378 names: &Names,
379 ) {
380 let Some(first) = body.first().copied() else {
381 return;
382 };
383 let name = ident_of(&first);
384 let rest = &body[1..];
385
386 if name == Some(names.r#if) {
389 let value = self.live() && self.eval(rest, hash, cx, names);
390 self.open(hash, value);
391 return;
392 }
393 if name == Some(names.ifdef) || name == Some(names.ifndef) {
394 let want = name == Some(names.ifdef);
395 let value = self.live() && self.defined_check(rest, hash, want, names);
396 self.open(hash, value);
397 return;
398 }
399 if name == Some(names.elif) || name == Some(names.elifdef) || name == Some(names.elifndef) {
400 self.elif(name, rest, hash, cx, names);
401 return;
402 }
403 if name == Some(names.r#else) {
404 self.branch_else(rest, hash);
405 return;
406 }
407 if name == Some(names.endif) {
408 self.endif(rest, hash);
409 return;
410 }
411 if !self.live() {
412 return;
416 }
417
418 if name.is_none() && decimal(&first, cx.interner).is_some() {
422 self.line_marker(body, hash, out.len(), cx);
423 return;
424 }
425
426 let interner = &mut *cx.interner;
427 if name == Some(names.define) {
428 let (def, diagnostics) = parse_define(rest, interner);
429 self.diagnostics.extend(diagnostics);
430 if let Some(def) = def {
431 if let Some(problem) = self.macros.define(def, interner) {
432 self.diagnostics.push(problem);
433 }
434 }
435 } else if name == Some(names.undef) {
436 self.undef(rest, hash, interner);
437 } else if name == Some(names.error) || name == Some(names.warning) {
438 self.message(rest, hash, name == Some(names.error), interner);
439 } else if name == Some(names.line) {
440 self.line(rest, hash, out.len(), cx);
441 } else if name == Some(names.pragma) {
442 if rest.len() == 1 && ident_of(&rest[0]) == Some(names.once) {
449 self.pragma_once(rest[0].span);
450 } else if !self.macro_stack_pragma(rest, hash, interner, names) {
451 self.pass_through(body, hash, out);
452 }
453 } else if name == Some(names.include) || name == Some(names.include_next) {
454 self.include(rest, hash, name == Some(names.include_next), out, cx, names);
455 } else if name == Some(names.embed) {
456 self.embed(rest, hash, out, cx);
457 } else {
458 self.diagnostics.push(
459 Diagnostic::error("invalid preprocessing directive", first.span).with_code("E0332"),
460 );
461 }
462 }
463
464 fn macro_stack_pragma(
476 &mut self,
477 rest: &[PpToken],
478 at: Span,
479 interner: &mut Interner,
480 names: &Names,
481 ) -> bool {
482 let which = match rest.first().and_then(ident_of) {
483 Some(name) if name == names.push_macro => names.push_macro,
484 Some(name) if name == names.pop_macro => names.pop_macro,
485 _ => return false,
486 };
487 let word = if which == names.push_macro { "push_macro" } else { "pop_macro" };
488 let [_, open, text, close, extra @ ..] = rest else {
493 self.invalid_pragma(word, at);
494 return true;
495 };
496 if open.punct() != Some(Punct::LParen)
497 || text.kind != PpTokenKind::StringLit
498 || close.punct() != Some(Punct::RParen)
499 {
500 self.invalid_pragma(word, at);
501 return true;
502 }
503 self.extra_tokens(extra, "#pragma");
504 let Some(name) = identifier_in(*text, interner) else {
509 return true;
510 };
511 if which == names.push_macro {
512 self.macros.push_macro(name);
513 } else {
514 self.macros.pop_macro(name);
515 }
516 true
517 }
518
519 fn invalid_pragma(&mut self, word: &str, at: Span) {
520 self.diagnostics.push(
521 Diagnostic::error(format!("invalid `#pragma {word}` directive"), at).with_code("E0672"),
522 );
523 }
524
525 fn pragma_once(&mut self, at: Span) {
527 if self.stack.len() <= 1 {
532 self.diagnostics.push(
533 Diagnostic::warning("`#pragma once` in the main file", at).with_code("W0332"),
534 );
535 }
536 if let Some(frame) = self.stack.last() {
537 self.seen.insert(frame.id.clone(), Guard::Once);
538 }
539 }
540
541 fn skip(&self, id: &Path) -> bool {
543 match self.seen.get(id) {
544 Some(Guard::Once) => true,
545 Some(Guard::Macro(name)) => self.macros.is_defined(*name),
546 None => false,
547 }
548 }
549
550 fn pass_through(&mut self, body: &[PpToken], hash: Span, out: &mut Vec<Tok>) {
552 let _ = self;
553 out.push(Tok::synthetic(
554 PpTokenKind::Punct(Punct::Hash),
555 None,
556 TokenFlags::START_OF_LINE,
557 hash,
558 ));
559 for (at, token) in body.iter().copied().enumerate() {
564 let mut token = Tok::new(token);
565 if at == 0 {
566 token.flags = token.flags.without(TokenFlags::LEADING_SPACE);
567 }
568 out.push(token);
569 }
570 }
571
572 fn include(
574 &mut self,
575 rest: &[PpToken],
576 hash: Span,
577 is_next: bool,
578 out: &mut Vec<Tok>,
579 cx: &mut Context<'_>,
580 names: &Names,
581 ) {
582 let Some(header) = self.header_of(rest, hash, cx) else {
583 return;
584 };
585 let (form, relative_to, from) = self.where_to_look(&header, is_next, cx);
586 let found = cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from);
587 let Some(found) = found else {
588 let tried = cx.search.tried(&header.name, form, relative_to.as_deref(), from);
589 let where_looked = if tried.is_empty() && Path::new(&header.name).is_absolute() {
593 "the name is an absolute path, so the search path was not used".to_owned()
594 } else if tried.is_empty() {
595 "the include search path is empty".to_owned()
596 } else {
597 let list: Vec<String> =
598 tried.iter().map(|d| d.to_string_lossy().into_owned()).collect();
599 format!("searched: {}", list.join(", "))
600 };
601 self.diagnostics.push(
602 Diagnostic::error(format!("`{}` file not found", header.name), hash)
603 .with_code("E0341")
604 .note(where_looked, hash),
605 );
606 return;
607 };
608 let id = cx.fs.identity(&found.path);
613 if self.skip(&id) {
614 return;
615 }
616 if self.stack.len() >= cx.max_include_depth as usize {
617 let mut diagnostic =
618 Diagnostic::error("`#include` nested too deeply", hash).with_code("E0342").note(
619 "a header that includes itself with no include guard is the usual cause",
620 hash,
621 );
622 if let Some(outer) = self.stack.first().filter(|f| !f.at.is_dummy()) {
623 diagnostic = diagnostic.note("the outermost include is here", outer.at);
624 }
625 self.diagnostics.push(diagnostic);
626 return;
627 }
628 let added = cx.sources.add_shared(found.name.clone(), found.bytes.clone(), Some(hash));
629 let file = match added {
630 Ok(file) => file,
631 Err(full) => {
632 self.diagnostics.push(Diagnostic::error(full.to_string(), hash).with_code("E0344"));
633 return;
634 }
635 };
636 self.stack.push(Frame {
637 at: hash,
638 dir: found.path.parent().map(Path::to_path_buf),
639 id,
640 path: found.path,
641 next: found.next,
642 });
643 self.process(file, out, cx, names);
644 self.stack.pop();
645 }
646
647 fn embed(&mut self, rest: &[PpToken], hash: Span, out: &mut Vec<Tok>, cx: &mut Context<'_>) {
649 let Some((header, params)) = self.embed_line(rest, hash, cx) else {
650 return;
651 };
652 let Some(found) = self.find(&header, false, cx) else {
653 self.diagnostics.push(
654 Diagnostic::error(format!("`{}` resource not found", header.name), hash)
655 .with_code("E0341")
656 .note("an `#embed` resource is looked for on the include path", hash),
657 );
658 return;
659 };
660 embed::tokens(found.bytes.as_slice(), ¶ms, hash, cx.interner, out);
665 }
666
667 fn embed_line(
669 &mut self,
670 rest: &[PpToken],
671 hash: Span,
672 cx: &mut Context<'_>,
673 ) -> Option<(Header, embed::Params)> {
674 if rest.is_empty() {
675 self.bad_header(hash);
676 return None;
677 }
678 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
679 let line = if line[0].kind == PpTokenKind::HeaderName {
685 line
686 } else {
687 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
688 self.diagnostics.append(&mut self.expander.take_diagnostics());
689 expanded
690 };
691 let Some(used) = embed::header_length(&line) else {
692 self.bad_header(line.first().map_or(hash, |t| t.report_span()));
693 return None;
694 };
695 let header = if line[0].kind == PpTokenKind::HeaderName {
696 header_from_token(spelling(line[0], cx.interner))
697 } else {
698 let spellings: Vec<&str> =
699 line[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
700 header_from_tokens(&spellings)
701 };
702 let Some(header) = header else {
703 self.bad_header(line[0].report_span());
704 return None;
705 };
706 let params = self.embed_params(&line[used..], hash, cx)?;
707 Some((header, params))
708 }
709
710 fn embed_params(
712 &mut self,
713 line: &[Tok],
714 at: Span,
715 cx: &mut Context<'_>,
716 ) -> Option<embed::Params> {
717 let Preprocessor { expander, macros, diagnostics, .. } = self;
718 let sources = &mut *cx.sources;
719 let mut expand = |toks: Vec<Tok>, interner: &mut Interner| {
720 expander.expand_toks(toks, macros, interner, sources)
721 };
722 let params = embed::parse(line, at, cx.interner, diagnostics, &mut expand);
723 self.diagnostics.append(&mut self.expander.take_diagnostics());
724 params
725 }
726
727 fn where_to_look(
738 &self,
739 header: &Header,
740 is_next: bool,
741 cx: &Context<'_>,
742 ) -> (IncludeForm, Option<PathBuf>, usize) {
743 let form = if header.angled { IncludeForm::Angled } else { IncludeForm::Quoted };
744 let frame = self.stack.last();
745 let from = if is_next {
746 frame.map_or(0, |f| f.next).max(cx.search.start(form))
747 } else {
748 cx.search.start(form)
749 };
750 let relative_to = if is_next { None } else { frame.and_then(|f| f.dir.clone()) };
751 (form, relative_to, from)
752 }
753
754 fn find(&self, header: &Header, is_next: bool, cx: &Context<'_>) -> Option<Found> {
756 let (form, relative_to, from) = self.where_to_look(header, is_next, cx);
757 cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from)
758 }
759
760 fn header_of(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>) -> Option<Header> {
762 if let Some(first) = rest.first().copied() {
763 if first.kind == PpTokenKind::HeaderName {
764 let text = first.value.map_or("", |v| cx.interner.resolve(v));
765 let header = header_from_token(text);
766 if header.is_none() {
767 self.bad_header(first.span);
768 }
769 self.extra_tokens(&rest[1..], "#include");
770 return header;
771 }
772 }
773 if rest.is_empty() {
777 self.bad_header(hash);
778 return None;
779 }
780 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
781 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
782 self.diagnostics.append(&mut self.expander.take_diagnostics());
783 let spellings: Vec<&str> = expanded.iter().map(|t| spelling(*t, cx.interner)).collect();
784 let header = header_from_tokens(&spellings);
785 if header.is_none() {
786 let at = expanded.first().map_or(hash, |t| t.report_span());
787 self.bad_header(at);
788 }
789 header
790 }
791
792 fn bad_operand(&mut self, tok: Tok, at: Span, interner: &Interner) {
794 self.diagnostics.push(
795 Diagnostic::error(
796 format!("expected an identifier as the operand of `{}`", spelling(tok, interner)),
797 at,
798 )
799 .with_code("E0345"),
800 );
801 }
802
803 fn bad_header(&mut self, at: Span) {
804 self.diagnostics.push(
805 Diagnostic::error("expected a file name in `<>` or `\"\"`", at).with_code("E0343"),
806 );
807 }
808
809 fn open(&mut self, span: Span, value: bool) {
811 let enclosing_live = self.live();
812 self.conds.push(Cond {
813 span,
814 live: enclosing_live && value,
815 taken: value,
816 enclosing_live,
817 seen_else: false,
818 });
819 }
820
821 fn elif(
822 &mut self,
823 name: Option<Symbol>,
824 rest: &[PpToken],
825 hash: Span,
826 cx: &mut Context<'_>,
827 names: &Names,
828 ) {
829 let Some(top) = self.conds.last() else {
830 self.stray("elif", hash);
831 return;
832 };
833 if top.seen_else {
834 self.diagnostics
835 .push(Diagnostic::error("`#elif` after `#else`", hash).with_code("E0333"));
836 return;
837 }
838 let (enclosing_live, already_taken) = (top.enclosing_live, top.taken);
841 let consider = enclosing_live && !already_taken;
842 let value = if !consider {
843 false
844 } else if name == Some(names.elif) {
845 self.eval(rest, hash, cx, names)
846 } else {
847 self.defined_check(rest, hash, name == Some(names.elifdef), names)
848 };
849 let top = self.conds.last_mut().expect("checked above and nothing popped");
850 top.live = consider && value;
851 top.taken = already_taken || value;
852 }
853
854 fn branch_else(&mut self, rest: &[PpToken], hash: Span) {
855 let Some(top) = self.conds.last_mut() else {
856 self.stray("else", hash);
857 return;
858 };
859 if top.seen_else {
860 self.diagnostics.push(Diagnostic::error("a second `#else`", hash).with_code("E0333"));
861 return;
862 }
863 top.live = top.enclosing_live && !top.taken;
864 top.taken = true;
865 top.seen_else = true;
866 let enclosing_live = top.enclosing_live;
867 if enclosing_live {
868 self.extra_tokens(rest, "#else");
869 }
870 }
871
872 fn endif(&mut self, rest: &[PpToken], hash: Span) {
873 if self.conds.pop().is_none() {
874 self.stray("endif", hash);
875 return;
876 }
877 if self.live() {
878 self.extra_tokens(rest, "#endif");
879 }
880 }
881
882 fn stray(&mut self, what: &str, hash: Span) {
883 self.diagnostics
884 .push(Diagnostic::error(format!("`#{what}` without `#if`"), hash).with_code("E0334"));
885 }
886
887 fn extra_tokens(&mut self, rest: &[PpToken], what: &str) {
892 if let Some(first) = rest.first() {
893 self.diagnostics.push(
894 Diagnostic::warning(format!("extra tokens after `{what}`"), first.span)
895 .with_code("W0330"),
896 );
897 }
898 }
899
900 fn eval(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>, names: &Names) -> bool {
902 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
903 let line = self.resolve_defined(line, cx.interner, names);
909 let line = self.resolve_has(line, cx, names, Pass::Headers);
914 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
915 self.diagnostics.append(&mut self.expander.take_diagnostics());
916 let line = self.resolve_defined(line, cx.interner, names);
917 let line = self.resolve_has(line, cx, names, Pass::Rest);
918 cond::evaluate(&line, cx.interner, &mut self.diagnostics, hash)
919 }
920
921 fn resolve_has(
926 &mut self,
927 line: Vec<Tok>,
928 cx: &mut Context<'_>,
929 names: &Names,
930 pass: Pass,
931 ) -> Vec<Tok> {
932 if !line.iter().any(|t| t.ident().is_some_and(|n| names.has.op(n).is_some())) {
933 return line;
934 }
935 let mut out = Vec::with_capacity(line.len());
936 let mut at = 0;
937 while at < line.len() {
938 let tok = line[at];
939 let op = tok.ident().and_then(|n| names.has.op(n));
940 let Some(op) = op.filter(|op| pass.answers(*op)) else {
941 if pass == Pass::Text && op.is_some_and(Op::is_header) {
942 self.outside_a_directive(tok, cx);
943 }
944 out.push(tok);
945 at += 1;
946 continue;
947 };
948 let Some((operand, after)) = arguments(&line, at + 1) else {
949 if pass != Pass::Headers {
953 self.diagnostics.push(
954 Diagnostic::error(
955 format!("expected `(` after `{}`", spelling(tok, cx.interner)),
956 tok.report_span(),
957 )
958 .with_code("E0345"),
959 );
960 }
961 out.push(tok);
962 at += 1;
963 continue;
964 };
965 at = after;
966 let value = self.ask(op, operand, tok, cx);
969 let sym = cx.interner.intern(&value.to_string());
970 out.push(Tok::synthetic(PpTokenKind::Number, Some(sym), tok.flags, tok.report_span()));
971 }
972 out
973 }
974
975 fn outside_a_directive(&mut self, tok: Tok, cx: &Context<'_>) {
983 self.diagnostics.push(
984 Diagnostic::error(
985 format!(
986 "`{}` used outside of a preprocessing directive",
987 spelling(tok, cx.interner)
988 ),
989 tok.report_span(),
990 )
991 .with_code("E0350"),
992 );
993 }
994
995 fn ask(&mut self, op: Op, operand: &[Tok], tok: Tok, cx: &mut Context<'_>) -> u32 {
997 let at = operand.first().map_or(tok.report_span(), |t| t.report_span());
998 match op {
999 Op::Include | Op::IncludeNext => {
1000 let spellings: Vec<&str> =
1001 operand.iter().map(|t| spelling(*t, cx.interner)).collect();
1002 let Some(header) = header_from_tokens(&spellings) else {
1003 self.bad_header(at);
1004 return 0;
1005 };
1006 u32::from(self.find(&header, op == Op::IncludeNext, cx).is_some())
1007 }
1008 Op::Embed => {
1009 let Some(used) = embed::header_length(operand) else {
1014 self.bad_header(at);
1015 return 0;
1016 };
1017 let header = if operand[0].kind == PpTokenKind::HeaderName {
1018 header_from_token(spelling(operand[0], cx.interner))
1019 } else {
1020 let spellings: Vec<&str> =
1021 operand[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
1022 header_from_tokens(&spellings)
1023 };
1024 let Some(header) = header else {
1025 self.bad_header(at);
1026 return 0;
1027 };
1028 let Some(params) = self.embed_params(&operand[used..], at, cx) else {
1033 return 0;
1034 };
1035 match self.find(&header, false, cx) {
1036 None => 0,
1037 Some(found) => {
1038 let taken = params.taken(found.bytes.as_slice().len() as u64);
1039 if taken == 0 { 2 } else { 1 }
1040 }
1041 }
1042 }
1043 Op::BuildingModule => {
1044 if attribute_name(operand, cx.interner).is_none() {
1045 self.bad_operand(tok, at, cx.interner);
1046 }
1047 0
1054 }
1055 Op::Table(kind) => {
1056 let Some(name) = attribute_name(operand, cx.interner) else {
1057 self.bad_operand(tok, at, cx.interner);
1058 return 0;
1059 };
1060 match kind {
1061 Kind::Attribute => rucc_gnu::has_attribute(name),
1062 Kind::CAttribute => rucc_gnu::has_c_attribute(name),
1063 Kind::Builtin => rucc_gnu::has_builtin(name),
1064 Kind::Feature => rucc_gnu::has_feature(name),
1065 Kind::Extension => rucc_gnu::has_extension(name),
1066 }
1067 }
1068 }
1069 }
1070
1071 fn resolve_defined(
1073 &mut self,
1074 line: Vec<Tok>,
1075 interner: &mut Interner,
1076 names: &Names,
1077 ) -> Vec<Tok> {
1078 if !line.iter().any(|t| t.ident() == Some(names.defined)) {
1079 return line;
1080 }
1081 let mut out = Vec::with_capacity(line.len());
1082 let mut at = 0;
1083 while at < line.len() {
1084 let tok = line[at];
1085 if tok.ident() != Some(names.defined) {
1086 out.push(tok);
1087 at += 1;
1088 continue;
1089 }
1090 let parenthesised = line.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1091 let name_at = if parenthesised { at + 2 } else { at + 1 };
1092 let name = line.get(name_at).and_then(|t| t.ident());
1093 let Some(name) = name else {
1094 self.diagnostics.push(
1095 Diagnostic::error("`defined` without a macro name", tok.report_span())
1096 .with_code("E0335"),
1097 );
1098 out.push(tok);
1099 at += 1;
1100 continue;
1101 };
1102 at = name_at + 1;
1103 if parenthesised {
1104 if line.get(at).is_some_and(|t| t.is(Punct::RParen)) {
1105 at += 1;
1106 } else {
1107 self.diagnostics.push(
1108 Diagnostic::error("expected `)` after `defined`", tok.report_span())
1109 .with_code("E0335"),
1110 );
1111 }
1112 }
1113 let value = self.macros.is_defined(name) || names.has.op(name).is_some();
1117 out.push(number(value, tok.flags, tok.report_span(), interner));
1118 }
1119 out
1120 }
1121
1122 fn defined_check(
1124 &mut self,
1125 rest: &[PpToken],
1126 hash: Span,
1127 want_defined: bool,
1128 names: &Names,
1129 ) -> bool {
1130 let Some(name) = rest.first().and_then(ident_of) else {
1131 self.diagnostics.push(
1132 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1133 .with_code("E0336"),
1134 );
1135 return false;
1136 };
1137 self.extra_tokens(&rest[1..], if want_defined { "#ifdef" } else { "#ifndef" });
1138 let defined = self.macros.is_defined(name) || names.has.op(name).is_some();
1139 defined == want_defined
1140 }
1141
1142 fn undef(&mut self, rest: &[PpToken], hash: Span, interner: &Interner) {
1143 let Some(name) = rest.first().and_then(ident_of) else {
1144 self.diagnostics.push(
1145 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1146 .with_code("E0336"),
1147 );
1148 return;
1149 };
1150 let text = interner.resolve(name);
1153 if text == "defined" || text.starts_with("__STDC_") {
1154 self.diagnostics.push(
1155 Diagnostic::error(format!("`{text}` cannot be undefined"), rest[0].span)
1156 .with_code("E0337"),
1157 );
1158 return;
1159 }
1160 self.macros.undef(name);
1161 self.extra_tokens(&rest[1..], "#undef");
1162 }
1163
1164 fn message(&mut self, rest: &[PpToken], hash: Span, fatal: bool, interner: &Interner) {
1166 let text = spell_line(rest, interner);
1167 let span = rest.first().map_or(hash, |t| t.span.to(last_span(rest)));
1168 let diag = if fatal {
1169 Diagnostic::error(text, span).with_code("E0338")
1170 } else {
1171 Diagnostic::warning(text, span).with_code("W0331")
1172 };
1173 self.diagnostics.push(diag);
1174 }
1175
1176 fn line(&mut self, rest: &[PpToken], hash: Span, at: usize, cx: &mut Context<'_>) {
1181 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
1182 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
1183 self.diagnostics.append(&mut self.expander.take_diagnostics());
1184 let interner = &mut *cx.interner;
1185
1186 let number_text = line
1187 .first()
1188 .filter(|t| t.kind == PpTokenKind::Number)
1189 .and_then(|t| t.value)
1190 .map(|v| interner.resolve(v));
1191 let Some(parsed) = number_text.and_then(|t| t.parse::<u64>().ok()) else {
1192 self.diagnostics.push(
1193 Diagnostic::error(
1194 "`#line` needs a decimal line number",
1195 line.first().map_or(hash, |t| t.report_span()),
1196 )
1197 .with_code("E0339"),
1198 );
1199 return;
1200 };
1201 if parsed == 0 || parsed > 2_147_483_647 {
1204 self.diagnostics.push(
1205 Diagnostic::error("`#line` number is out of range", line[0].report_span())
1206 .with_code("E0339"),
1207 );
1208 return;
1209 }
1210
1211 let mut file = None;
1212 if let Some(second) = line.get(1) {
1213 if second.kind == PpTokenKind::StringLit {
1214 file = second.value;
1215 } else {
1216 self.diagnostics.push(
1217 Diagnostic::error(
1218 "`#line` file name must be a string literal",
1219 second.report_span(),
1220 )
1221 .with_code("E0339"),
1222 );
1223 return;
1224 }
1225 }
1226 if let Some(extra) = line.get(2) {
1227 self.diagnostics.push(
1228 Diagnostic::warning("extra tokens after `#line`", extra.report_span())
1229 .with_code("W0330"),
1230 );
1231 }
1232 #[expect(
1233 clippy::cast_possible_truncation,
1234 reason = "the range check above keeps this inside i32, let alone u32"
1235 )]
1236 let number = parsed as u32;
1237 self.lines.push(LineDirective { span: hash, line: number, file, at });
1238 let name = file.map(|v| destringize(cx.interner.resolve(v)));
1239 cx.sources.set_presumed(hash.lo, number, name);
1240 }
1241
1242 fn line_marker(&mut self, body: &[PpToken], hash: Span, at: usize, cx: &mut Context<'_>) {
1259 let Some(number) = decimal(&body[0], cx.interner) else { return };
1260 let mut rest = &body[1..];
1261 let mut file = None;
1262 if let Some(first) = rest.first().filter(|t| t.kind == PpTokenKind::StringLit) {
1263 file = first.value;
1264 rest = &rest[1..];
1265 }
1266
1267 let (mut entering, mut leaving) = (false, false);
1268 for flag in rest {
1269 match decimal(flag, cx.interner) {
1270 Some(1) => entering = true,
1271 Some(2) => leaving = true,
1272 Some(3 | 4) => {}
1273 _ => {
1274 let text = spell_line(std::slice::from_ref(flag), cx.interner);
1275 self.diagnostics.push(
1276 Diagnostic::error(
1277 format!("invalid flag `{text}` in line directive"),
1278 flag.span,
1279 )
1280 .with_code("E0339"),
1281 );
1282 return;
1283 }
1284 }
1285 }
1286
1287 let name = file.map(|v| destringize(cx.interner.resolve(v)));
1288 if leaving {
1289 if let Some(name) = &name {
1290 if !self.leave_marker(name) {
1291 self.diagnostics.push(
1292 Diagnostic::warning(
1293 format!("file `{name}` linemarker ignored due to incorrect nesting"),
1294 last_span(body),
1295 )
1296 .with_code("W0330"),
1297 );
1298 return;
1299 }
1300 } else {
1301 self.markers.pop();
1302 }
1303 }
1304 if entering {
1305 let here = cx.sources.presumed(hash.lo).map(|loc| loc.name.to_owned());
1306 self.markers.push(here.unwrap_or_default());
1307 }
1308
1309 self.lines.push(LineDirective { span: hash, line: number, file, at });
1310 cx.sources.set_presumed(hash.lo, number, name);
1311 }
1312
1313 fn leave_marker(&mut self, name: &str) -> bool {
1327 if let Some(at) = self.markers.iter().rposition(|outer| outer == name) {
1328 self.markers.truncate(at);
1329 return true;
1330 }
1331 let found = self.stack.iter().rev().skip(1).any(|f| f.path.as_os_str() == name);
1334 if found {
1335 self.markers.clear();
1336 }
1337 found
1338 }
1339
1340 fn pragma_operator(
1346 &mut self,
1347 expanded: Vec<Tok>,
1348 out: &mut Vec<Tok>,
1349 interner: &mut Interner,
1350 names: &Names,
1351 ) {
1352 if !expanded.iter().any(|t| t.ident() == Some(names.pragma_op)) {
1353 out.extend(expanded);
1354 return;
1355 }
1356 let mut at = 0;
1357 let mut ends_a_line = false;
1362 while at < expanded.len() {
1363 let mut tok = expanded[at];
1364 if tok.ident() != Some(names.pragma_op) {
1365 if ends_a_line {
1366 tok.flags = tok.flags.with(TokenFlags::START_OF_LINE);
1367 ends_a_line = false;
1368 }
1369 out.push(tok);
1370 at += 1;
1371 continue;
1372 }
1373 let open = expanded.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1374 let text = expanded.get(at + 2).filter(|t| t.kind == PpTokenKind::StringLit);
1375 let close = expanded.get(at + 3).is_some_and(|t| t.is(Punct::RParen));
1376 let (Some(text), true, true) = (text, open, close) else {
1377 self.diagnostics.push(
1378 Diagnostic::error("`_Pragma` takes a single string literal", tok.report_span())
1379 .with_code("E0340"),
1380 );
1381 out.push(tok);
1382 at += 1;
1383 continue;
1384 };
1385 let literal = text.value.map(|v| interner.resolve(v)).unwrap_or_default();
1386 let body = destringize(literal);
1387 self.emit_pragma(&body, tok, out, interner, names);
1388 ends_a_line = true;
1389 at += 4;
1390 }
1391 }
1392
1393 fn emit_pragma(
1395 &mut self,
1396 body: &str,
1397 at: Tok,
1398 out: &mut Vec<Tok>,
1399 interner: &mut Interner,
1400 names: &Names,
1401 ) {
1402 let span = at.report_span();
1403 let (tokens, diagnostics) = tokenize(body.as_bytes(), 0, Options::new(), interner);
1404 self.diagnostics.extend(
1407 diagnostics
1408 .into_iter()
1409 .map(|d| Diagnostic::new(d.severity, d.message, span).with_code("E0340")),
1410 );
1411 let tokens: Vec<PpToken> = tokens.into_iter().filter(|t| !t.is_eof()).collect();
1412 if self.macro_stack_pragma(&tokens, span, interner, names) {
1416 return;
1417 }
1418 out.push(Tok::synthetic(
1419 PpTokenKind::Punct(Punct::Hash),
1420 None,
1421 TokenFlags::START_OF_LINE,
1422 span,
1423 ));
1424 out.push(Tok::synthetic(PpTokenKind::Ident, Some(names.pragma), TokenFlags::EMPTY, span));
1425 for (at, t) in tokens.into_iter().enumerate() {
1429 let spaced = at == 0 || t.flags.has(TokenFlags::LEADING_SPACE);
1432 let flags = if spaced {
1433 TokenFlags::EMPTY.with(TokenFlags::LEADING_SPACE)
1434 } else {
1435 TokenFlags::EMPTY
1436 };
1437 out.push(Tok::synthetic(t.kind, t.value, flags, span));
1438 }
1439 }
1440}
1441
1442fn guard_opener(body: &[PpToken], names: &Names) -> Option<Symbol> {
1447 let name = ident_of(body.first()?)?;
1448 let rest = &body[1..];
1449 if name == names.ifndef {
1450 let [only] = rest else {
1451 return None;
1452 };
1453 return ident_of(only);
1454 }
1455 if name != names.r#if {
1456 return None;
1457 }
1458 let [bang, defined, tail @ ..] = rest else {
1459 return None;
1460 };
1461 if bang.punct() != Some(Punct::Bang) || ident_of(defined) != Some(names.defined) {
1462 return None;
1463 }
1464 match tail {
1465 [only] => ident_of(only),
1466 [open, only, close]
1467 if open.punct() == Some(Punct::LParen) && close.punct() == Some(Punct::RParen) =>
1468 {
1469 ident_of(only)
1470 }
1471 _ => None,
1472 }
1473}
1474
1475fn is_include(name: Option<Symbol>, names: &Names) -> bool {
1477 name == Some(names.include) || name == Some(names.include_next) || name == Some(names.embed)
1478}
1479
1480fn is_directive(tok: PpToken) -> bool {
1482 tok.flags.has(TokenFlags::START_OF_LINE) && tok.punct() == Some(Punct::Hash)
1483}
1484
1485fn ident_of(tok: &PpToken) -> Option<Symbol> {
1486 match tok.kind {
1487 PpTokenKind::Ident => tok.value,
1488 _ => None,
1489 }
1490}
1491
1492fn last_span(tokens: &[PpToken]) -> Span {
1493 tokens.last().map_or(Span::DUMMY, |t| t.span)
1494}
1495
1496fn decimal(tok: &PpToken, interner: &Interner) -> Option<u32> {
1502 if tok.kind != PpTokenKind::Number {
1503 return None;
1504 }
1505 let text = interner.resolve(tok.value?);
1506 if text.is_empty() || !text.bytes().all(|b| b.is_ascii_digit()) {
1507 return None;
1508 }
1509 text.parse::<u32>().ok().filter(|n| *n <= 2_147_483_647)
1512}
1513
1514fn number(value: bool, flags: TokenFlags, span: Span, interner: &mut Interner) -> Tok {
1516 let sym = interner.intern(if value { "1" } else { "0" });
1517 Tok::synthetic(PpTokenKind::Number, Some(sym), flags, span)
1518}
1519
1520fn spell_line(tokens: &[PpToken], interner: &Interner) -> String {
1522 let mut out = String::new();
1523 for (index, tok) in tokens.iter().enumerate() {
1524 if index > 0 && tok.flags.has(TokenFlags::LEADING_SPACE) {
1525 out.push(' ');
1526 }
1527 match tok.value {
1528 Some(sym) => out.push_str(interner.resolve(sym)),
1529 None => {
1530 if let Some(p) = tok.punct() {
1531 out.push_str(p.as_str());
1532 }
1533 }
1534 }
1535 }
1536 out
1537}
1538
1539fn identifier_in(text: PpToken, interner: &mut Interner) -> Option<Symbol> {
1544 let literal = interner.resolve(text.value?).to_string();
1545 let (tokens, _) = tokenize(destringize(&literal).as_bytes(), 0, Options::new(), interner);
1546 let mut real = tokens.into_iter().filter(|t| !t.is_eof());
1547 let first = real.next()?;
1548 if first.kind != PpTokenKind::Ident || real.next().is_some() {
1549 return None;
1550 }
1551 first.value
1552}
1553
1554fn destringize(literal: &str) -> String {
1559 let body = literal
1560 .trim_start_matches(['L', 'u', 'U', '8'])
1561 .strip_prefix('"')
1562 .and_then(|s| s.strip_suffix('"'))
1563 .unwrap_or(literal);
1564 let mut out = String::with_capacity(body.len());
1565 let mut chars = body.chars();
1566 while let Some(c) = chars.next() {
1567 if c != '\\' {
1568 out.push(c);
1569 continue;
1570 }
1571 match chars.next() {
1572 Some('"') => out.push('"'),
1573 Some('\\') => out.push('\\'),
1574 Some(other) => {
1575 out.push('\\');
1576 out.push(other);
1577 }
1578 None => out.push('\\'),
1579 }
1580 }
1581 out
1582}
1583
1584fn arguments(line: &[Tok], at: usize) -> Option<(&[Tok], usize)> {
1590 if !line.get(at)?.is(Punct::LParen) {
1591 return None;
1592 }
1593 let mut depth = 1u32;
1594 let mut end = at + 1;
1595 while end < line.len() {
1596 if line[end].is(Punct::LParen) {
1597 depth += 1;
1598 } else if line[end].is(Punct::RParen) {
1599 depth -= 1;
1600 if depth == 0 {
1601 return Some((&line[at + 1..end], end + 1));
1602 }
1603 }
1604 end += 1;
1605 }
1606 None
1607}
1608
1609fn attribute_name<'i>(operand: &[Tok], interner: &'i Interner) -> Option<&'i str> {
1615 let name = match operand {
1616 [one] => one,
1617 [_, scope, name] if scope.is(Punct::ColonColon) => name,
1618 _ => return None,
1619 };
1620 name.ident().map(|sym| interner.resolve(sym))
1621}
1622
1623#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1630enum Pass {
1631 Headers,
1633 Rest,
1636 Text,
1638}
1639
1640impl Pass {
1641 fn answers(self, op: Op) -> bool {
1643 match self {
1644 Pass::Headers => op.is_header(),
1645 Pass::Rest => true,
1646 Pass::Text => !op.is_header(),
1647 }
1648 }
1649}
1650
1651#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1653enum Op {
1654 Include,
1656 IncludeNext,
1658 Embed,
1661 BuildingModule,
1663 Table(Kind),
1665}
1666
1667impl Op {
1668 fn is_header(self) -> bool {
1670 matches!(self, Op::Include | Op::IncludeNext | Op::Embed)
1671 }
1672}
1673
1674struct HasOps {
1679 ops: [(Symbol, Op); 9],
1680 range: (Symbol, Symbol),
1687}
1688
1689impl HasOps {
1690 fn new(interner: &mut Interner) -> HasOps {
1691 let ops = [
1692 (interner.intern("__has_include"), Op::Include),
1693 (interner.intern("__has_include_next"), Op::IncludeNext),
1694 (interner.intern("__has_embed"), Op::Embed),
1695 (interner.intern("__has_attribute"), Op::Table(Kind::Attribute)),
1696 (interner.intern("__has_c_attribute"), Op::Table(Kind::CAttribute)),
1697 (interner.intern("__has_builtin"), Op::Table(Kind::Builtin)),
1698 (interner.intern("__has_feature"), Op::Table(Kind::Feature)),
1699 (interner.intern("__has_extension"), Op::Table(Kind::Extension)),
1700 (interner.intern("__building_module"), Op::BuildingModule),
1701 ];
1702 let mut range = (ops[0].0, ops[0].0);
1703 for &(sym, _) in &ops {
1704 range = (range.0.min(sym), range.1.max(sym));
1705 }
1706 HasOps { ops, range }
1707 }
1708
1709 #[inline]
1711 fn op(&self, name: Symbol) -> Option<Op> {
1712 if name < self.range.0 || name > self.range.1 {
1713 return None;
1714 }
1715 self.ops.iter().find(|(sym, _)| *sym == name).map(|(_, op)| *op)
1716 }
1717}
1718
1719struct Names {
1725 define: Symbol,
1726 undef: Symbol,
1727 r#if: Symbol,
1728 ifdef: Symbol,
1729 ifndef: Symbol,
1730 elif: Symbol,
1731 elifdef: Symbol,
1732 elifndef: Symbol,
1733 r#else: Symbol,
1734 endif: Symbol,
1735 line: Symbol,
1736 error: Symbol,
1737 warning: Symbol,
1738 pragma: Symbol,
1739 include: Symbol,
1740 include_next: Symbol,
1741 embed: Symbol,
1742 defined: Symbol,
1743 once: Symbol,
1744 push_macro: Symbol,
1745 pop_macro: Symbol,
1746 pragma_op: Symbol,
1747 has: HasOps,
1748}
1749
1750impl Names {
1751 fn new(interner: &mut Interner) -> Names {
1752 Names {
1753 define: interner.intern("define"),
1754 undef: interner.intern("undef"),
1755 r#if: interner.intern("if"),
1756 ifdef: interner.intern("ifdef"),
1757 ifndef: interner.intern("ifndef"),
1758 elif: interner.intern("elif"),
1759 elifdef: interner.intern("elifdef"),
1760 elifndef: interner.intern("elifndef"),
1761 r#else: interner.intern("else"),
1762 endif: interner.intern("endif"),
1763 line: interner.intern("line"),
1764 error: interner.intern("error"),
1765 warning: interner.intern("warning"),
1766 pragma: interner.intern("pragma"),
1767 include: interner.intern("include"),
1768 include_next: interner.intern("include_next"),
1769 embed: interner.intern("embed"),
1770 defined: interner.intern("defined"),
1771 once: interner.intern("once"),
1772 push_macro: interner.intern("push_macro"),
1773 pop_macro: interner.intern("pop_macro"),
1774 pragma_op: interner.intern("_Pragma"),
1775 has: HasOps::new(interner),
1776 }
1777 }
1778}
1779
1780#[cfg(test)]
1781mod tests {
1782 use rucc_diag::{Severity, SourceMap};
1783 use rucc_session::{MemoryFileSystem, SearchPath};
1784
1785 use super::*;
1786 use rucc_session::Std;
1787
1788 use crate::predef::Timestamp;
1789
1790 struct Run {
1795 interner: Interner,
1796 sources: SourceMap,
1797 fs: MemoryFileSystem,
1798 search: SearchPath,
1799 pp: Preprocessor,
1800 }
1801
1802 impl Run {
1803 fn new() -> Run {
1804 Run {
1805 interner: Interner::new(),
1806 sources: SourceMap::new(),
1807 fs: MemoryFileSystem::new(),
1808 search: SearchPath::new(),
1809 pp: Preprocessor::new(),
1810 }
1811 }
1812
1813 fn file(&mut self, path: &str, contents: &str) {
1815 self.fs.insert(path, contents.as_bytes().to_vec());
1816 }
1817
1818 fn bytes(&mut self, path: &str, contents: &[u8]) {
1821 self.fs.insert(path, contents.to_vec());
1822 }
1823
1824 fn dir(&mut self, path: &str) {
1826 self.search.push_bracket(path);
1827 }
1828
1829 fn predefine(&mut self, triple: &str, opts: &Predef) {
1831 let target = TargetInfo::new(triple.parse().expect("a supported triple"));
1832 let mut cx =
1833 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1834 self.pp.predefine(&target, opts, &mut cx).expect("the map has room");
1835 }
1836
1837 fn go(&mut self, src: &str) -> String {
1839 self.go_named("/main.c", src)
1840 }
1841
1842 fn raw(&mut self, src: &str) -> Vec<Tok> {
1844 let file = self.sources.add("/main.c", src.as_bytes().to_vec()).expect("room");
1845 let mut cx =
1846 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1847 self.pp.run(file, &mut cx)
1848 }
1849
1850 fn go_named(&mut self, path: &str, src: &str) -> String {
1852 let file = self.sources.add(path, src.as_bytes().to_vec()).expect("the map has room");
1853 let out = {
1854 let mut cx =
1855 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1856 self.pp.run(file, &mut cx)
1857 };
1858 let mut text = String::new();
1859 for (at, tok) in out.iter().enumerate() {
1860 let spaced = tok.flags.has(TokenFlags::LEADING_SPACE)
1861 || tok.flags.has(TokenFlags::START_OF_LINE);
1862 if at > 0 && spaced {
1863 text.push(' ');
1864 }
1865 match tok.kind {
1866 PpTokenKind::Punct(p) => text.push_str(p.as_str()),
1867 _ => text.push_str(
1868 self.interner.resolve(tok.value.expect("every non-punctuator interns")),
1869 ),
1870 }
1871 }
1872 text
1873 }
1874
1875 fn files(&self) -> usize {
1879 self.sources.files().len()
1880 }
1881
1882 fn messages(&mut self) -> Vec<String> {
1883 self.pp.take_diagnostics().into_iter().map(|d| d.message).collect()
1884 }
1885
1886 fn severities(&mut self) -> Vec<Severity> {
1887 self.pp.diagnostics().iter().map(|d| d.severity).collect()
1888 }
1889 }
1890
1891 fn clean(src: &str) -> String {
1892 let mut run = Run::new();
1893 let text = run.go(src);
1894 assert!(run.messages().is_empty(), "expected no diagnostics from {src:?}");
1895 text
1896 }
1897
1898 #[test]
1899 fn a_taken_branch_is_kept_and_the_other_is_not() {
1900 assert_eq!(clean("#if 1\nyes\n#else\nno\n#endif\n"), "yes");
1901 assert_eq!(clean("#if 0\nyes\n#else\nno\n#endif\n"), "no");
1902 }
1903
1904 #[test]
1905 fn ifdef_and_ifndef_ask_the_macro_table() {
1906 assert_eq!(clean("#define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
1907 assert_eq!(clean("#ifdef F\nyes\n#endif\n"), "");
1908 assert_eq!(clean("#ifndef F\nyes\n#endif\n"), "yes");
1909 assert_eq!(clean("#define F 1\n#if 0\na\n#elifdef F\nb\n#endif\n"), "b");
1911 assert_eq!(clean("#if 0\na\n#elifndef F\nb\n#endif\n"), "b");
1912 }
1913
1914 #[test]
1915 fn only_the_first_true_branch_of_a_chain_is_taken() {
1916 assert_eq!(clean("#if 0\na\n#elif 1\nb\n#elif 1\nc\n#else\nd\n#endif\n"), "b");
1917 assert_eq!(clean("#if 0\na\n#elif 0\nb\n#else\nc\n#endif\n"), "c");
1918 }
1919
1920 #[test]
1921 fn a_branch_after_one_that_was_taken_is_not_evaluated() {
1922 assert_eq!(clean("#if 1\na\n#elif 1/0\nb\n#endif\n"), "a");
1925 }
1926
1927 #[test]
1928 fn a_skipped_region_is_not_read_for_anything_but_nesting() {
1929 let src = "#if 0\nthis is not C at all\n#frobnicate\n#define\n#if 1\ninner\n#endif\n#endif\nafter\n";
1931 assert_eq!(clean(src), "after");
1932 }
1933
1934 #[test]
1935 fn nesting_inside_a_dead_branch_stays_balanced() {
1936 let src = "#if 0\n#ifdef X\na\n#else\nb\n#endif\n#else\nc\n#endif\n";
1937 assert_eq!(clean(src), "c");
1938 }
1939
1940 #[test]
1941 fn defined_works_in_both_spellings_and_before_expansion() {
1942 assert_eq!(clean("#define F 0\n#if defined F\nyes\n#endif\n"), "yes");
1943 assert_eq!(clean("#define F 0\n#if defined(F)\nyes\n#endif\n"), "yes");
1944 assert_eq!(clean("#if defined(F)\nyes\n#endif\n"), "");
1945 assert_eq!(clean("#define F 0\n#if defined F && !F\nyes\n#endif\n"), "yes");
1948 }
1949
1950 #[test]
1951 fn an_identifier_that_survived_expansion_is_zero() {
1952 assert_eq!(clean("#if NOT_DEFINED_ANYWHERE\nyes\n#else\nno\n#endif\n"), "no");
1953 assert_eq!(clean("#if !NOT_DEFINED_ANYWHERE\nyes\n#endif\n"), "yes");
1954 }
1955
1956 #[test]
1957 fn short_circuiting_keeps_a_guarded_expression_safe() {
1958 assert_eq!(clean("#if defined(F) && 1/F\nyes\n#else\nno\n#endif\n"), "no");
1961 assert_eq!(clean("#if 1 ? 2 : 1/0\nyes\n#endif\n"), "yes");
1962 }
1963
1964 #[test]
1965 fn the_operators_have_the_precedence_they_do_in_c() {
1966 assert_eq!(clean("#if 1 + 2 * 3 == 7\nyes\n#endif\n"), "yes");
1967 assert_eq!(clean("#if (1 + 2) * 3 == 9\nyes\n#endif\n"), "yes");
1968 assert_eq!(clean("#if 1 << 4 == 16\nyes\n#endif\n"), "yes");
1969 assert_eq!(clean("#if -8 / 3 == -2\nyes\n#endif\n"), "yes");
1970 assert_eq!(clean("#if (0xff & 0x0f) == 15\nyes\n#endif\n"), "yes");
1971 }
1972
1973 #[test]
1974 fn an_unsigned_operand_makes_the_whole_comparison_unsigned() {
1975 assert_eq!(clean("#if -1 < 0u\nyes\n#else\nno\n#endif\n"), "no");
1979 assert_eq!(clean("#if -1 < 0\nyes\n#else\nno\n#endif\n"), "yes");
1980 }
1981
1982 #[test]
1983 fn character_constants_evaluate() {
1984 assert_eq!(clean("#if 'A' == 65\nyes\n#endif\n"), "yes");
1985 assert_eq!(clean("#if '\\n' == 10\nyes\n#endif\n"), "yes");
1986 }
1987
1988 #[test]
1989 fn a_macro_is_expanded_before_the_expression_is_evaluated() {
1990 assert_eq!(clean("#define V 3\n#if V > 2\nyes\n#endif\n"), "yes");
1991 assert_eq!(clean("#define M(a) ((a) * 2)\n#if M(3) == 6\nyes\n#endif\n"), "yes");
1992 }
1993
1994 #[test]
1995 fn an_invocation_may_span_lines_within_a_run_of_text() {
1996 assert_eq!(clean("#define M(a, b) a + b\nM(1,\n2)\n"), "1 + 2");
1997 }
1998
1999 #[test]
2000 fn undef_removes_a_definition() {
2001 assert_eq!(clean("#define F 1\n#undef F\n#ifdef F\nyes\n#else\nno\n#endif\n"), "no");
2002 assert_eq!(clean("#undef NEVER_DEFINED\nok\n"), "ok");
2005 }
2006
2007 #[test]
2008 fn some_names_cannot_be_undefined() {
2009 let mut run = Run::new();
2010 run.go("#undef defined\n");
2011 assert_eq!(run.messages(), vec!["`defined` cannot be undefined".to_owned()]);
2012 }
2013
2014 #[test]
2015 fn error_reports_the_rest_of_the_line() {
2016 let mut run = Run::new();
2017 run.go("#if 0\n#error not this one\n#else\n#error unsupported target\n#endif\n");
2018 assert_eq!(run.messages(), vec!["unsupported target".to_owned()]);
2019 }
2020
2021 #[test]
2022 fn warning_is_a_warning() {
2023 let mut run = Run::new();
2024 run.go("#warning this is fine\n");
2025 assert_eq!(run.severities(), vec![Severity::Warning]);
2026 assert_eq!(run.messages(), vec!["this is fine".to_owned()]);
2027 }
2028
2029 #[test]
2030 fn an_unterminated_conditional_is_reported() {
2031 let mut run = Run::new();
2032 assert_eq!(run.go("#if 1\nyes\n"), "yes");
2033 assert_eq!(run.messages(), vec!["unterminated `#if`".to_owned()]);
2034 }
2035
2036 #[test]
2037 fn a_conditional_without_an_if_is_reported() {
2038 let mut run = Run::new();
2039 run.go("#endif\n");
2040 assert_eq!(run.messages(), vec!["`#endif` without `#if`".to_owned()]);
2041
2042 let mut run = Run::new();
2043 run.go("#if 1\n#else\n#else\n#endif\n");
2044 assert_eq!(run.messages(), vec!["a second `#else`".to_owned()]);
2045
2046 let mut run = Run::new();
2047 run.go("#if 1\n#else\n#elif 1\n#endif\n");
2048 assert_eq!(run.messages(), vec!["`#elif` after `#else`".to_owned()]);
2049 }
2050
2051 #[test]
2052 fn tokens_after_endif_are_a_warning_rather_than_an_error() {
2053 let mut run = Run::new();
2056 assert_eq!(run.go("#if 1\nyes\n#endif FOO\n"), "yes");
2057 assert_eq!(run.severities(), vec![Severity::Warning]);
2058 assert_eq!(run.messages(), vec!["extra tokens after `#endif`".to_owned()]);
2059 }
2060
2061 #[test]
2062 fn the_null_directive_does_nothing() {
2063 assert_eq!(clean("#\na\n#\nb\n"), "a b");
2064 }
2065
2066 #[test]
2067 fn an_unknown_directive_is_an_error_when_the_region_is_live() {
2068 let mut run = Run::new();
2069 run.go("#frobnicate\n");
2070 assert_eq!(run.messages(), vec!["invalid preprocessing directive".to_owned()]);
2071 }
2072
2073 #[test]
2074 fn line_is_recorded_for_the_source_map() {
2075 let mut run = Run::new();
2076 run.go("#line 42 \"other.c\"\n");
2077 assert!(run.messages().is_empty());
2078 let recorded = run.pp.line_directives();
2079 assert_eq!(recorded.len(), 1);
2080 assert_eq!(recorded[0].line, 42);
2081 let file = recorded[0].file.expect("a file name was given");
2082 assert_eq!(run.interner.resolve(file), "\"other.c\"");
2083 }
2084
2085 #[test]
2086 fn line_moves_what_line_and_file_the_lines_after_it_are_on() {
2087 let mut run = Run::new();
2088 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2089 assert_eq!(run.go("#line 1000\n__LINE__ __FILE__\n__LINE__\n"), "1000 \"/main.c\" 1001");
2090 }
2091
2092 #[test]
2093 fn a_line_marker_moves_the_lines_after_it_the_way_line_does() {
2094 let mut run = Run::new();
2095 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2096 assert_eq!(run.go("# 200 \"xyz\"\n__FILE__ __LINE__\n"), "\"xyz\" 200");
2097 }
2098
2099 #[test]
2100 fn a_line_marker_with_no_name_leaves_the_name_alone() {
2101 let mut run = Run::new();
2102 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2103 assert_eq!(run.go("# 20\n__FILE__ __LINE__\n"), "\"/main.c\" 20");
2104 }
2105
2106 #[test]
2107 fn a_line_marker_may_say_line_zero() {
2108 let mut run = Run::new();
2111 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2112 assert_eq!(run.go("# 0 \"xyz\"\n__LINE__\n"), "0");
2113 }
2114
2115 #[test]
2116 fn entering_and_returning_are_a_nesting_the_marker_flags_keep() {
2117 let mut run = Run::new();
2118 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2119 let text =
2120 run.go("# 200 \"xyz\" 1\n__FILE__\n# 5 \"/main.c\" 2\n__FILE__ __LINE__\n# 9 3 4\n");
2121 assert_eq!(text, "\"xyz\" \"/main.c\" 5");
2122 assert!(run.messages().is_empty());
2123 }
2124
2125 #[test]
2126 fn returning_to_a_file_nothing_was_ever_in_is_ignored() {
2127 let mut run = Run::new();
2128 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2129 assert_eq!(run.go("# 200 \"xyz\" 2 3\n__FILE__ __LINE__\n"), "\"/main.c\" 2");
2130 assert_eq!(
2131 run.messages(),
2132 vec!["file `xyz` linemarker ignored due to incorrect nesting".to_owned()]
2133 );
2134 }
2135
2136 #[test]
2137 fn a_flag_that_is_not_one_of_the_four_is_an_error() {
2138 let mut run = Run::new();
2139 run.go("# 20 \"a\" 7\n");
2140 assert_eq!(run.messages(), vec!["invalid flag `7` in line directive".to_owned()]);
2141 }
2142
2143 #[test]
2144 fn a_hash_and_something_that_is_not_a_line_number_is_still_an_unknown_directive() {
2145 let mut run = Run::new();
2147 run.go("# 1.5 \"a\"\n");
2148 assert_eq!(run.messages(), vec!["invalid preprocessing directive".to_owned()]);
2149 }
2150
2151 #[test]
2152 fn a_name_on_the_directive_is_the_name_from_there_on() {
2153 let mut run = Run::new();
2154 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2155 assert_eq!(run.go("#line 7 \"gen.y\"\n__FILE__ __LINE__\n"), "\"gen.y\" 7");
2156 }
2157
2158 #[test]
2159 fn a_directive_with_no_name_keeps_the_one_already_in_force() {
2160 let mut run = Run::new();
2161 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2162 assert_eq!(run.go("#line 7 \"gen.y\"\n#line 20\n__FILE__ __LINE__\n"), "\"gen.y\" 20");
2163 }
2164
2165 #[test]
2166 fn the_number_is_expanded_first_because_line_plus_one_is_real_code() {
2167 let mut run = Run::new();
2168 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2169 assert_eq!(run.go("#define WHERE 300\n#line WHERE\n__LINE__\n"), "300");
2170 }
2171
2172 #[test]
2173 fn a_directive_in_a_header_does_not_move_the_file_that_included_it() {
2174 let mut run = Run::new();
2175 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2176 run.file("/h.h", "#line 500\n__LINE__\n");
2177 run.dir("/");
2178 assert_eq!(run.go("#include <h.h>\n__LINE__\n"), "500 2");
2179 }
2180
2181 #[test]
2182 fn extra_tokens_after_the_file_name_are_a_warning_and_not_an_error() {
2183 let mut run = Run::new();
2184 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2185 assert_eq!(run.go("#line 7 \"gen.y\" and more\n__LINE__\n"), "7");
2186 assert_eq!(run.messages(), vec!["extra tokens after `#line`".to_owned()]);
2187 }
2188
2189 #[test]
2190 fn a_line_number_out_of_range_is_refused() {
2191 let mut run = Run::new();
2192 run.go("#line 0\n");
2193 assert_eq!(run.messages(), vec!["`#line` number is out of range".to_owned()]);
2194
2195 let mut run = Run::new();
2196 run.go("#line notanumber\n");
2197 assert_eq!(run.messages(), vec!["`#line` needs a decimal line number".to_owned()]);
2198 }
2199
2200 #[test]
2201 fn a_pragma_passes_through_unchanged() {
2202 assert_eq!(clean("#pragma pack(1)\nint x;\n"), "#pragma pack(1) int x;");
2203 }
2204
2205 #[test]
2210 fn the_space_between_the_hash_and_the_word_comes_off_a_pragma_that_is_indented() {
2211 assert_eq!(clean("#if 1\n# pragma pack(1)\n#endif\n"), "#pragma pack(1)");
2212 assert_eq!(clean("# pragma pack( 1 )\n"), "#pragma pack( 1 )", "the rest is kept");
2213 }
2214
2215 #[test]
2216 fn the_pragma_operator_becomes_a_pragma() {
2217 assert_eq!(
2218 clean("_Pragma(\"GCC visibility push(default)\")\nint x;\n"),
2219 "#pragma GCC visibility push(default) int x;"
2220 );
2221 }
2222
2223 #[test]
2224 fn the_pragma_operator_works_from_inside_a_macro() {
2225 let src = "#define PUSH _Pragma(\"pack(push)\")\nPUSH\nint x;\n";
2228 assert_eq!(clean(src), "#pragma pack(push) int x;");
2229 }
2230
2231 #[test]
2235 fn what_follows_a_pragma_operator_starts_a_line() {
2236 let mut run = Run::new();
2237 let out = run.raw("int x; _Pragma(\"pack(1)\") int y;\n");
2238 let starts: Vec<_> =
2239 out.iter().map(|tok| tok.flags.has(TokenFlags::START_OF_LINE)).collect();
2240 assert_eq!(
2243 starts,
2244 vec![true, false, false, true, false, false, false, false, false, true, false, false]
2245 );
2246 }
2247
2248 #[test]
2249 fn a_pragma_operator_that_is_not_given_a_string_is_reported() {
2250 let mut run = Run::new();
2251 run.go("_Pragma(x)\n");
2252 assert_eq!(run.messages(), vec!["`_Pragma` takes a single string literal".to_owned()]);
2253 }
2254
2255 #[test]
2256 fn an_include_reads_the_file_it_names() {
2257 let mut run = Run::new();
2258 run.file("/dir/one.h", "int from_the_header;\n");
2259 run.dir("/dir");
2260 assert_eq!(run.go("#include <one.h>\nint after;\n"), "int from_the_header; int after;");
2261 assert!(run.messages().is_empty());
2262 }
2263
2264 #[test]
2265 fn a_quoted_include_looks_next_to_the_including_file_first() {
2266 let mut run = Run::new();
2267 run.file("/local.h", "beside\n");
2268 run.file("/dir/local.h", "on the path\n");
2269 run.dir("/dir");
2270 assert_eq!(run.go("#include \"local.h\"\n"), "beside");
2271 assert!(run.messages().is_empty());
2272 }
2273
2274 #[test]
2275 fn an_angled_include_does_not_look_next_to_the_including_file() {
2276 let mut run = Run::new();
2277 run.file("/local.h", "beside\n");
2278 run.file("/dir/local.h", "on the path\n");
2279 run.dir("/dir");
2280 assert_eq!(run.go("#include <local.h>\n"), "on the path");
2281 }
2282
2283 #[test]
2284 fn a_macro_defined_in_a_header_is_visible_after_the_include() {
2285 let mut run = Run::new();
2286 run.file("/dir/defs.h", "#define N 42\n");
2287 run.dir("/dir");
2288 assert_eq!(run.go("#include <defs.h>\nint a = N;\n"), "int a = 42;");
2289 assert!(run.messages().is_empty());
2290 }
2291
2292 #[test]
2293 fn an_include_guard_keeps_the_second_read_empty() {
2294 let mut run = Run::new();
2295 run.file("/dir/g.h", "#ifndef G\n#define G\nonce\n#endif\n");
2296 run.dir("/dir");
2297 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
2298 assert!(run.messages().is_empty());
2299 assert_eq!(run.files(), 2, "the second include is not opened at all");
2300 }
2301
2302 #[test]
2303 fn the_other_spelling_of_a_guard_is_recognised_too() {
2304 for guard in ["#if !defined(G)", "#if !defined G"] {
2305 let mut run = Run::new();
2306 run.file("/dir/g.h", &format!("{guard}\n#define G\nonce\n#endif\n"));
2307 run.dir("/dir");
2308 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
2309 assert_eq!(run.files(), 2, "{guard} should be a guard");
2310 }
2311 }
2312
2313 #[test]
2314 fn a_conditional_that_is_not_a_guard_does_not_skip_anything() {
2315 let mut run = Run::new();
2318 run.file("/dir/g.h", "#ifndef G\ntwice\n#endif\n");
2319 run.dir("/dir");
2320 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "twice twice");
2321 assert_eq!(run.files(), 3);
2322 }
2323
2324 #[test]
2325 fn a_token_outside_the_guard_stops_it_being_a_guard() {
2326 let mut run = Run::new();
2327 run.file("/dir/g.h", "#ifndef G\n#define G\n#endif\nalways\n");
2328 run.dir("/dir");
2329 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "always always");
2330 assert_eq!(run.files(), 3);
2331 }
2332
2333 #[test]
2334 fn pragma_once_skips_the_second_read_and_does_not_reach_the_output() {
2335 let mut run = Run::new();
2336 run.file("/dir/o.h", "#pragma once\nonce\n");
2337 run.dir("/dir");
2338 assert_eq!(run.go("#include <o.h>\n#include <o.h>\n"), "once");
2339 assert!(run.messages().is_empty());
2340 assert_eq!(run.files(), 2);
2341 }
2342
2343 #[test]
2344 fn pragma_once_in_the_main_file_is_a_warning_and_is_still_applied() {
2345 let mut run = Run::new();
2349 let src = "#pragma once\n#include <s.c>\nbody\n";
2350 run.file("/dir/s.c", src);
2351 run.dir("/dir");
2352 assert_eq!(run.go_named("/dir/s.c", src), "body");
2353 assert_eq!(run.severities(), vec![Severity::Warning]);
2354 assert_eq!(run.messages(), vec!["`#pragma once` in the main file".to_owned()]);
2355 assert_eq!(run.files(), 1);
2356 }
2357
2358 #[test]
2359 fn pragma_once_holds_across_two_spellings_of_the_one_path() {
2360 let mut run = Run::new();
2363 run.file("dir/s.c", "#pragma once\nbody\n");
2364 run.dir(".");
2365 assert_eq!(run.go("#include <dir/s.c>\n#include <dir/s.c>\n"), "body");
2366 assert!(run.messages().is_empty());
2367 assert_eq!(run.files(), 2);
2368 }
2369
2370 #[test]
2371 fn any_other_pragma_still_passes_through() {
2372 assert_eq!(clean("#pragma once_upon_a_time\n"), "#pragma once_upon_a_time");
2373 }
2374
2375 #[test]
2378 fn push_macro_and_pop_macro_put_a_definition_aside_and_bring_it_back() {
2379 let src = "#define X 1\n#pragma push_macro(\"X\")\n#undef X\n#define X 2\n a X\n#pragma pop_macro(\"X\")\nb X\n";
2380 assert_eq!(clean(src), "a 2 b 1");
2381 }
2382
2383 #[test]
2384 fn a_name_with_no_definition_pushes_and_pops_the_absence() {
2385 let src = "#pragma push_macro(\"X\")\n#define X 1\na X\n#pragma pop_macro(\"X\")\nb X\n";
2388 assert_eq!(clean(src), "a 1 b X");
2389 }
2390
2391 #[test]
2392 fn the_pushes_nest() {
2393 let src = "#define X 1\n#pragma push_macro(\"X\")\n#undef X\n#define X 2\n #pragma push_macro(\"X\")\n#undef X\n#define X 3\n a X\n#pragma pop_macro(\"X\")\nb X\n#pragma pop_macro(\"X\")\nc X\n";
2394 assert_eq!(clean(src), "a 3 b 2 c 1");
2395 }
2396
2397 #[test]
2398 fn a_pop_with_nothing_pushed_says_nothing() {
2399 assert_eq!(clean("#define X 1\n#pragma pop_macro(\"X\")\nX\n"), "1");
2402 assert_eq!(clean("#pragma pop_macro(\"Never\")\nx\n"), "x");
2403 }
2404
2405 #[test]
2406 fn the_pragma_operator_spelling_works_and_takes_effect_where_it_is_written() {
2407 let src = "#define X 1\n_Pragma(\"push_macro(\\\"X\\\")\")\n#undef X\n#define X 2\n a X\n_Pragma(\"pop_macro(\\\"X\\\")\")\nb X\n";
2412 assert_eq!(clean(src), "a 2 b 1");
2413 }
2414
2415 #[test]
2416 fn the_gcc_spelling_is_not_one_of_these_and_passes_through() {
2417 let src = "#define X 1\n#pragma GCC push_macro(\"X\")\n#undef X\n#define X 2\nX\n";
2421 assert_eq!(clean(src), "#pragma GCC push_macro(\"X\") 2");
2422 }
2423
2424 #[test]
2425 fn a_push_macro_that_is_not_the_shape_is_an_error() {
2426 for src in ["#pragma push_macro\n", "#pragma push_macro(X)\n", "#pragma pop_macro()\n"] {
2427 let mut run = Run::new();
2428 run.go(src);
2429 let word = if src.contains("push") { "push" } else { "pop" };
2430 assert_eq!(
2431 run.messages(),
2432 vec![format!("invalid `#pragma {word}_macro` directive")],
2433 "from {src:?}"
2434 );
2435 }
2436 }
2437
2438 #[test]
2439 fn a_string_that_does_not_spell_one_identifier_names_no_macro() {
2440 assert_eq!(clean("#pragma push_macro(\"a b\")\nx\n"), "x");
2443 assert_eq!(clean("#pragma push_macro(\"2\")\nx\n"), "x");
2444 }
2445
2446 #[test]
2447 fn what_follows_the_closing_parenthesis_is_the_usual_warning() {
2448 let mut run = Run::new();
2449 assert_eq!(run.go("#define X 1\n#pragma push_macro(\"X\") junk\nX\n"), "1");
2450 assert_eq!(run.severities(), vec![Severity::Warning]);
2451 assert_eq!(run.messages(), vec!["extra tokens after `#pragma`".to_owned()]);
2452 }
2453
2454 #[test]
2455 fn has_include_answers_from_the_search_path() {
2456 let mut run = Run::new();
2457 run.file("/dir/there.h", "");
2458 run.dir("/dir");
2459 let src = "#if __has_include(<there.h>)\nyes\n#endif\n\
2460 #if __has_include(<gone.h>)\nno\n#endif\n";
2461 assert_eq!(run.go(src), "yes");
2462 assert!(run.messages().is_empty(), "a header that is not there is an answer, not an error");
2463 }
2464
2465 #[test]
2466 fn has_include_asks_the_question_the_include_on_the_same_line_would() {
2467 let mut run = Run::new();
2471 run.file("/beside.h", "");
2472 let src = "#if __has_include(\"beside.h\")\nquoted\n#endif\n\
2473 #if __has_include(<beside.h>)\nangled\n#endif\n";
2474 assert_eq!(run.go(src), "quoted");
2475 }
2476
2477 #[test]
2478 fn has_include_next_starts_where_include_next_would() {
2479 let mut run = Run::new();
2480 run.file("/a/both.h", "#if __has_include_next(<both.h>)\nmore\n#endif\n");
2481 run.file("/b/both.h", "last\n");
2482 run.file("/a/only.h", "#if __has_include_next(<only.h>)\nmore\n#endif\n");
2483 run.dir("/a");
2484 run.dir("/b");
2485 assert_eq!(run.go("#include <both.h>\n"), "more");
2486 assert_eq!(run.go("#include <only.h>\n"), "", "there is nothing after /a to find it in");
2487 }
2488
2489 #[test]
2490 fn the_operand_of_has_include_is_not_macro_expanded() {
2491 let mut run = Run::new();
2494 run.file("/dir/linux/version.h", "");
2495 run.dir("/dir");
2496 let src = "#define linux 1\n#if __has_include(<linux/version.h>)\nyes\n#endif\n";
2497 assert_eq!(run.go(src), "yes");
2498 }
2499
2500 #[test]
2501 fn a_macro_may_expand_to_a_has_include() {
2502 let mut run = Run::new();
2504 run.file("/dir/there.h", "");
2505 run.dir("/dir");
2506 let src = "#define HAVE __has_include(<there.h>)\n#if HAVE\nyes\n#endif\n";
2507 assert_eq!(run.go(src), "yes");
2508 }
2509
2510 #[test]
2511 fn defined_says_the_has_operators_are_there() {
2512 let src = "#if defined(__has_include) && defined __has_builtin\nyes\n#endif\n";
2515 assert_eq!(clean(src), "yes");
2516 assert_eq!(clean("#ifdef __has_attribute\nyes\n#endif\n"), "yes");
2517 }
2518
2519 #[test]
2520 fn has_attribute_answers_out_of_the_matrix() {
2521 assert_eq!(clean("#if __has_attribute(packed)\nyes\n#endif\n"), "");
2524 assert_eq!(clean("#if __has_attribute(no_such_attribute)\nyes\n#endif\n"), "");
2525 assert_eq!(clean("#if !__has_attribute(packed)\nno\n#endif\n"), "no");
2526 }
2527
2528 #[test]
2529 fn the_scoped_spelling_of_an_attribute_is_the_same_question() {
2530 assert_eq!(clean("#if __has_c_attribute(gnu::packed)\nyes\n#endif\n"), "");
2534 assert_eq!(clean("#if __has_c_attribute(deprecated)\nyes\n#endif\n"), "");
2535 }
2536
2537 #[test]
2538 fn has_builtin_answers_no_until_the_builtin_is_real() {
2539 assert_eq!(clean("#if __has_builtin(__builtin_expect)\nyes\n#endif\n"), "");
2540 assert_eq!(clean("#if __has_builtin(__builtin_nonesuch)\nyes\n#endif\n"), "");
2541 }
2542
2543 #[test]
2544 fn has_feature_and_has_extension_read_the_same_table() {
2545 assert_eq!(clean("#if __has_feature(pragma_once)\nyes\n#endif\n"), "yes");
2548 assert_eq!(clean("#if __has_extension(pragma_once)\nyes\n#endif\n"), "yes");
2549 assert_eq!(clean("#if __has_extension(include_next)\nyes\n#endif\n"), "yes");
2550 assert_eq!(clean("#if __has_feature(include_next)\nyes\n#endif\n"), "");
2551 assert_eq!(clean("#if __has_feature(statement_expressions)\nyes\n#endif\n"), "");
2552 }
2553
2554 #[test]
2555 fn building_module_is_always_no_and_is_recognised_so_that_the_line_parses() {
2556 assert_eq!(clean("#if __building_module(m)\nyes\n#endif\n"), "");
2560 assert_eq!(clean("#if !__building_module(m)\nyes\n#endif\n"), "yes");
2561 assert_eq!(
2562 clean(
2563 "#if !defined(offsetof) || (__has_feature(modules) && !__building_module(x))\nyes\n#endif\n"
2564 ),
2565 "yes"
2566 );
2567 assert_eq!(clean("#ifdef __building_module\nyes\n#endif\n"), "yes");
2569 assert_eq!(clean("#if defined(__building_module)\nyes\n#endif\n"), "yes");
2570 }
2571
2572 #[test]
2573 fn a_has_operator_without_an_operand_is_reported() {
2574 let mut run = Run::new();
2575 run.go("#if __has_include\nyes\n#endif\n");
2576 assert_eq!(run.messages(), ["expected `(` after `__has_include`"]);
2577 let mut run = Run::new();
2578 run.go("#if __has_include(1)\nyes\n#endif\n");
2579 assert_eq!(run.messages(), ["expected a file name in `<>` or `\"\"`"]);
2580 let mut run = Run::new();
2581 run.go("#if __has_attribute(\"packed\")\nyes\n#endif\n");
2582 assert_eq!(run.messages(), ["expected an identifier as the operand of `__has_attribute`"]);
2583 }
2584
2585 #[test]
2586 fn the_has_operators_answer_in_ordinary_text_too() {
2587 assert_eq!(clean("f __has_feature(pragma_once)\n"), "f 1");
2591 assert_eq!(clean("b __has_builtin(__builtin_expect)\n"), "b 0");
2592 assert_eq!(clean("a __has_attribute(packed)\n"), "a 0");
2593 assert_eq!(clean("c __has_c_attribute(deprecated)\n"), "c 0");
2594 assert_eq!(clean("m __building_module(foo)\n"), "m 0");
2595 }
2596
2597 #[test]
2598 fn a_macro_that_expands_to_a_has_operator_is_answered_where_it_is_used() {
2599 assert_eq!(clean("#define HAVE __has_feature(pragma_once)\nx HAVE\n"), "x 1");
2602 assert_eq!(clean("#define HAVE(x) __has_attribute(x)\ny HAVE(packed)\n"), "y 0");
2603 }
2604
2605 #[test]
2606 fn a_has_operator_in_text_still_needs_its_operand() {
2607 let mut run = Run::new();
2608 run.go("tail __has_attribute;\n");
2609 assert_eq!(run.messages(), ["expected `(` after `__has_attribute`"]);
2610 }
2611
2612 #[test]
2613 fn the_header_operators_are_refused_in_ordinary_text() {
2614 let mut run = Run::new();
2617 run.file("/dir/there.h", "");
2618 run.dir("/dir");
2619 run.go("a __has_include(<there.h>)\n");
2620 assert_eq!(run.messages(), ["`__has_include` used outside of a preprocessing directive"]);
2621 let mut run = Run::new();
2622 run.go("b __has_include_next(\"x.h\")\n");
2623 assert_eq!(
2624 run.messages(),
2625 ["`__has_include_next` used outside of a preprocessing directive"]
2626 );
2627 }
2628
2629 #[test]
2630 fn the_predefined_set_is_visible_to_the_source_file() {
2631 let mut run = Run::new();
2632 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2633 let src = "#if defined(__x86_64__) && defined(__linux__) && __SIZEOF_LONG__ == 8\n\
2634 yes\n#endif\n";
2635 assert_eq!(run.go(src), "yes");
2636 assert!(run.messages().is_empty());
2637 }
2638
2639 #[test]
2640 fn the_predefined_set_follows_the_target_and_not_the_host() {
2641 let mut run = Run::new();
2642 run.predefine("aarch64-unknown-linux-gnu", &Predef::new());
2643 assert_eq!(
2644 run.go("#ifdef __x86_64__\nno\n#endif\n#ifdef __aarch64__\nyes\n#endif\n"),
2645 "yes"
2646 );
2647 }
2648
2649 #[test]
2650 fn a_predefined_macro_expands_where_it_is_used() {
2651 let mut run = Run::new();
2652 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2653 assert_eq!(run.go("__SIZE_TYPE__ n;\n"), "long unsigned int n;");
2654 }
2655
2656 #[test]
2657 fn a_command_line_define_is_a_definition_like_any_other() {
2658 let mut opts = Predef::new();
2659 opts.defines = vec!["FOO".to_owned(), "BAR=3".to_owned()];
2660 opts.undefines = vec!["__linux__".to_owned()];
2661 let mut run = Run::new();
2662 run.predefine("x86_64-unknown-linux-gnu", &opts);
2663 let src = "#if FOO && BAR == 3 && !defined(__linux__)\nyes\n#endif\n";
2664 assert_eq!(run.go(src), "yes");
2665 assert!(run.messages().is_empty());
2666 }
2667
2668 #[test]
2669 fn the_predefined_set_produces_no_tokens_of_its_own() {
2670 let mut run = Run::new();
2673 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2674 assert_eq!(run.go("alone\n"), "alone");
2675 }
2676
2677 #[test]
2678 fn the_predefined_files_are_named_the_way_gcc_names_them() {
2679 let mut run = Run::new();
2680 let mut opts = Predef::new();
2681 opts.defines = vec!["FOO=1".to_owned()];
2682 run.predefine("x86_64-unknown-linux-gnu", &opts);
2683 let names: Vec<&str> = run.sources.files().iter().map(|f| f.name.as_str()).collect();
2684 assert_eq!(names, ["<built-in>", "<command-line>"]);
2685 }
2686
2687 #[test]
2688 fn a_dialect_without_the_gnu_extensions_says_so() {
2689 let mut opts = Predef::new();
2690 opts.gnu_extensions = false;
2691 opts.std = Std::C99;
2692 let mut run = Run::new();
2693 run.predefine("x86_64-unknown-linux-gnu", &opts);
2694 let src = "#if defined(__STRICT_ANSI__) && __STDC_VERSION__ == 199901L && !defined(linux)\n\
2695 yes\n#endif\n";
2696 assert_eq!(run.go(src), "yes");
2697 }
2698
2699 #[test]
2700 fn the_date_and_time_are_the_same_for_the_whole_translation_unit() {
2701 let mut opts = Predef::new();
2702 opts.timestamp = Timestamp::from_unix(0);
2703 let mut run = Run::new();
2704 run.predefine("x86_64-unknown-linux-gnu", &opts);
2705 assert_eq!(run.go("__DATE__ __TIME__\n"), "\"Jan 1 1970\" \"00:00:00\"");
2706 }
2707
2708 #[test]
2709 fn a_has_operator_in_a_dead_branch_is_not_asked_about() {
2710 assert_eq!(clean("#if 0\n#if __has_include\n#endif\n#endif\nafter\n"), "after");
2712 }
2713
2714 #[test]
2715 fn a_conditional_may_not_span_an_include() {
2716 let mut run = Run::new();
2720 run.file("/dir/open.h", "#if 1\n");
2721 run.dir("/dir");
2722 run.go("#include <open.h>\nkept\n#endif\n");
2723 let messages = run.messages();
2724 assert_eq!(messages.len(), 2);
2725 assert!(messages[0].contains("unterminated"));
2726 assert!(messages[1].contains("without"));
2727 }
2728
2729 #[test]
2730 fn include_next_continues_after_the_directory_the_file_came_from() {
2731 let mut run = Run::new();
2734 run.file("/a/limits.h", "wrapper\n#include_next <limits.h>\n");
2735 run.file("/b/limits.h", "real\n");
2736 run.dir("/a");
2737 run.dir("/b");
2738 assert_eq!(run.go("#include <limits.h>\n"), "wrapper real");
2739 assert!(run.messages().is_empty());
2740 }
2741
2742 #[test]
2743 fn a_computed_include_is_expanded_first() {
2744 let mut run = Run::new();
2745 run.file("/dir/sub/thing.h", "computed\n");
2746 run.dir("/dir");
2747 let src = "#define HEADER <sub/thing.h>\n#include HEADER\n";
2748 assert_eq!(run.go(src), "computed");
2749 assert!(run.messages().is_empty());
2750 let mut run = Run::new();
2752 run.file("/dir/sub/thing.h", "computed\n");
2753 run.dir("/dir");
2754 assert_eq!(run.go("#define H \"sub/thing.h\"\n#include H\n"), "computed");
2755 }
2756
2757 #[test]
2758 fn a_header_that_is_not_there_says_where_it_looked() {
2759 let mut run = Run::new();
2760 run.dir("/dir");
2761 run.go("#include <nope.h>\n");
2762 let diagnostics = run.pp.take_diagnostics();
2763 assert_eq!(diagnostics.len(), 1);
2764 assert_eq!(diagnostics[0].code, Some("E0341"));
2765 assert_eq!(diagnostics[0].message, "`nope.h` file not found");
2766 assert!(diagnostics[0].children[0].message.contains("/dir"));
2767 }
2768
2769 #[test]
2770 fn an_include_that_is_not_a_header_name_is_reported() {
2771 let mut run = Run::new();
2772 run.go("#include 3\n");
2773 let diagnostics = run.pp.take_diagnostics();
2774 assert_eq!(diagnostics[0].code, Some("E0343"));
2775 }
2776
2777 #[test]
2778 fn a_header_that_includes_itself_stops() {
2779 let mut run = Run::new();
2780 run.file("/dir/loop.h", "#include <loop.h>\n");
2781 run.dir("/dir");
2782 run.go("#include <loop.h>\n");
2783 let diagnostics = run.pp.take_diagnostics();
2784 assert_eq!(diagnostics.len(), 1, "one complaint, not one per level");
2785 assert_eq!(diagnostics[0].code, Some("E0342"));
2786 }
2787
2788 #[test]
2789 fn an_include_in_a_dead_branch_is_not_read() {
2790 let mut run = Run::new();
2791 assert_eq!(run.go("#if 0\n#include <nothing.h>\n#endif\nafter\n"), "after");
2792 assert!(run.messages().is_empty(), "a skipped include is not resolved");
2793 }
2794
2795 #[test]
2796 fn embed_writes_the_bytes_of_the_resource() {
2797 let mut run = Run::new();
2798 run.bytes("/logo.bin", &[0, 1, 127, 128, 255]);
2799 assert_eq!(run.go("#embed \"logo.bin\"\n"), "0, 1, 127, 128, 255");
2800 assert!(run.messages().is_empty());
2801 }
2802
2803 #[test]
2804 fn an_embed_is_a_valid_initializer_on_both_sides_of_empty() {
2805 let mut run = Run::new();
2810 run.bytes("/some.bin", &[7, 8]);
2811 run.bytes("/none.bin", &[]);
2812 let line = |name: &str| {
2813 format!("{{\n#embed \"{name}\" prefix(0xEF,) suffix(,0xFE) if_empty(0)\n}}\n")
2814 };
2815 assert_eq!(run.go(&line("some.bin")), "{ 0xEF,7, 8 ,0xFE }");
2816 assert_eq!(run.go_named("/other.c", &line("none.bin")), "{ 0 }");
2817 assert!(run.messages().is_empty());
2818 }
2819
2820 #[test]
2821 fn the_limit_and_the_offset_choose_a_window_of_the_resource() {
2822 let mut run = Run::new();
2823 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
2824 assert_eq!(run.go("#embed \"eight.bin\" limit(3)\n"), "1, 2, 3");
2825 assert_eq!(
2826 run.go_named("/b.c", "#embed \"eight.bin\" gnu::offset(4) limit(3)\n"),
2827 "5, 6, 7"
2828 );
2829 assert_eq!(run.go_named("/c.c", "#embed \"eight.bin\" limit(0) if_empty(9)\n"), "9");
2832 assert_eq!(run.go_named("/d.c", "#embed \"eight.bin\" gnu::offset(99)\n"), "");
2833 assert!(run.messages().is_empty());
2834 }
2835
2836 #[test]
2837 fn the_limit_is_a_constant_expression_and_not_just_a_number() {
2838 let mut run = Run::new();
2841 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
2842 assert_eq!(
2843 run.go("#define CHUNK 2\n#embed \"eight.bin\" limit(CHUNK * 2)\n"),
2844 "1, 2, 3, 4"
2845 );
2846 assert!(run.messages().is_empty());
2847 }
2848
2849 #[test]
2850 fn a_misspelled_embed_parameter_is_refused_rather_than_ignored() {
2851 let mut run = Run::new();
2854 run.bytes("/eight.bin", &[1, 2]);
2855 assert_eq!(run.go("#embed \"eight.bin\" limits(1)\n"), "");
2856 assert_eq!(run.messages(), vec!["unknown `#embed` parameter `limits`".to_owned()]);
2857 let mut vendor = Run::new();
2858 vendor.bytes("/eight.bin", &[1, 2]);
2859 assert_eq!(vendor.go("#embed \"eight.bin\" clang::offset(1)\n"), "");
2860 assert_eq!(
2861 vendor.messages(),
2862 vec!["unknown `#embed` parameter `clang::offset`".to_owned()]
2863 );
2864 }
2865
2866 #[test]
2867 fn a_missing_embed_resource_is_reported_as_a_resource() {
2868 let mut run = Run::new();
2869 run.go("#embed <nothing.bin>\n");
2870 assert_eq!(run.messages(), vec!["`nothing.bin` resource not found".to_owned()]);
2871 }
2872
2873 #[test]
2874 fn has_embed_tells_missing_from_present_from_empty() {
2875 let mut run = Run::new();
2879 run.bytes("/some.bin", &[1]);
2880 run.bytes("/none.bin", &[]);
2881 let src = "#if __has_embed(\"none.bin\") == __STDC_EMBED_EMPTY__\nempty\n#endif\n\
2882 #if __has_embed(\"some.bin\") == __STDC_EMBED_FOUND__\nfound\n#endif\n\
2883 #if __has_embed(\"gone.bin\") == __STDC_EMBED_NOT_FOUND__\ngone\n#endif\n";
2884 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
2885 assert_eq!(run.go(src), "empty found gone");
2886 assert!(run.messages().is_empty());
2887 }
2888
2889 #[test]
2890 fn has_embed_takes_the_limit_into_account() {
2891 let mut run = Run::new();
2894 run.bytes("/some.bin", &[1, 2, 3]);
2895 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
2896 let src = "#if __has_embed(\"some.bin\" limit(0)) == __STDC_EMBED_EMPTY__\nempty\n#endif\n";
2897 assert_eq!(run.go(src), "empty");
2898 assert!(run.messages().is_empty());
2899 }
2900
2901 #[test]
2902 fn a_directive_may_have_space_before_the_hash_and_after_it() {
2903 assert_eq!(clean(" # define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
2904 }
2905
2906 #[test]
2907 fn a_definition_survives_across_a_conditional() {
2908 assert_eq!(clean("#if 1\n#define F 7\n#endif\nF\n"), "7");
2909 }
2910
2911 #[test]
2912 fn an_empty_if_expression_is_reported() {
2913 let mut run = Run::new();
2914 run.go("#if\n#endif\n");
2915 assert_eq!(run.messages(), vec!["`#if` with no expression".to_owned()]);
2916 }
2917
2918 #[test]
2919 fn the_file_and_the_line_say_where_the_use_is() {
2920 let mut run = Run::new();
2921 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2922 assert_eq!(run.go("__FILE__ __LINE__\n__LINE__\n"), "\"/main.c\" 1 2");
2923 assert!(run.messages().is_empty());
2924 }
2925
2926 #[test]
2927 fn a_macro_that_mentions_the_line_answers_with_the_call() {
2928 let mut run = Run::new();
2929 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2930 run.file("/where.h", "#define WHERE __FILE__ __LINE__\n");
2931 assert_eq!(run.go("#include \"where.h\"\n\n\nWHERE\n"), "\"/main.c\" 4");
2935 assert!(run.messages().is_empty());
2936 }
2937
2938 #[test]
2939 fn the_file_name_is_the_file_without_the_directories() {
2940 let mut run = Run::new();
2941 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2942 assert_eq!(run.go_named("/deep/down/main.c", "__FILE_NAME__\n"), "\"main.c\"");
2943 }
2944
2945 #[test]
2946 fn a_backslash_in_the_name_is_escaped() {
2947 let mut run = Run::new();
2948 run.predefine("x86_64-pc-windows-msvc", &Predef::new());
2949 let text = run.go_named("C:\\src\\main.c", "__FILE__ __FILE_NAME__\n");
2952 assert_eq!(text, "\"C:\\\\src\\\\main.c\" \"main.c\"");
2953 }
2954
2955 #[test]
2956 fn the_base_file_is_the_one_named_on_the_command_line() {
2957 let mut run = Run::new();
2958 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2959 run.file("/deep.h", "__FILE__ __BASE_FILE__\n");
2960 assert_eq!(run.go("#include \"deep.h\"\n"), "\"/deep.h\" \"/main.c\"");
2961 assert!(run.messages().is_empty());
2962 }
2963
2964 #[test]
2965 fn the_include_level_counts_the_headers_above_it() {
2966 let mut run = Run::new();
2967 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2968 run.file("/one.h", "__INCLUDE_LEVEL__\n#include \"two.h\"\n");
2969 run.file("/two.h", "__INCLUDE_LEVEL__\n");
2970 assert_eq!(run.go("__INCLUDE_LEVEL__\n#include \"one.h\"\n"), "0 1 2");
2971 assert!(run.messages().is_empty());
2972 }
2973
2974 #[test]
2975 fn the_counter_is_a_different_number_every_time() {
2976 let mut run = Run::new();
2977 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2978 assert_eq!(run.go("__COUNTER__ __COUNTER__ __COUNTER__\n"), "0 1 2");
2979 }
2980
2981 #[test]
2982 fn the_counter_advances_once_per_argument_rather_than_once_per_use() {
2983 let mut run = Run::new();
2984 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2985 assert_eq!(run.go("#define TWICE(x) x x\nTWICE(__COUNTER__) __COUNTER__\n"), "0 0 1");
2989 }
2990
2991 #[test]
2992 fn the_line_is_a_number_an_if_can_use() {
2993 let mut run = Run::new();
2994 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2995 assert_eq!(run.go("#if __LINE__ == 1 && __INCLUDE_LEVEL__ == 0\nyes\n#endif\n"), "yes");
2996 assert!(run.messages().is_empty());
2997 }
2998
2999 #[test]
3000 fn the_dynamic_macros_are_defined_like_any_others() {
3001 let mut run = Run::new();
3002 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3003 let src = "#ifdef __FILE__\nyes\n#endif\n#undef __LINE__\n#ifndef __LINE__\ngone\n#endif\n";
3004 assert_eq!(run.go(src), "yes gone");
3005 assert!(run.messages().is_empty(), "`#undef` of a builtin is allowed, as it is in GCC");
3006 }
3007
3008 #[test]
3009 fn redefining_a_dynamic_macro_warns_and_points_at_the_built_in_file() {
3010 let mut run = Run::new();
3011 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3012 assert_eq!(run.go("#define __FILE__ \"mine.c\"\n__FILE__\n"), "\"mine.c\"");
3013 let complaints = run.pp.take_diagnostics();
3014 assert_eq!(complaints.len(), 1);
3015 assert_eq!(complaints[0].code, Some("W0301"));
3016 let previous = complaints[0].children.first().expect("a note saying where it was");
3017 assert_eq!(run.sources.lookup(previous.span.lo).map(|loc| loc.file), {
3018 let built_in = run.sources.files().iter().find(|f| f.name == BUILT_IN);
3019 built_in.map(|f| f.id)
3020 });
3021 }
3022
3023 #[test]
3024 fn destringizing_undoes_what_stringizing_did() {
3025 assert_eq!(destringize(r#""a \"b\" c""#), r#"a "b" c"#);
3026 assert_eq!(destringize(r#""a \\ b""#), r"a \ b");
3027 assert_eq!(destringize(r#"L"wide""#), "wide");
3028 }
3029}