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 seen: HashMap<PathBuf, Guard>,
114}
115
116impl Preprocessor {
117 pub fn new() -> Preprocessor {
119 Preprocessor::default()
120 }
121
122 pub fn macros(&self) -> &MacroTable {
124 &self.macros
125 }
126
127 pub fn macros_mut(&mut self) -> &mut MacroTable {
129 &mut self.macros
130 }
131
132 pub fn diagnostics(&self) -> &[Diagnostic] {
134 &self.diagnostics
135 }
136
137 pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
139 std::mem::take(&mut self.diagnostics)
140 }
141
142 pub fn line_directives(&self) -> &[LineDirective] {
148 &self.lines
149 }
150
151 pub fn predefine(
163 &mut self,
164 target: &TargetInfo,
165 opts: &Predef,
166 cx: &mut Context<'_>,
167 ) -> Result<(), SourceMapFull> {
168 let names = Names::new(cx.interner);
169 let file = self.synthetic(BUILT_IN, built_in(target, opts), cx, &names)?;
170 let start = cx.sources.file(file).start;
176 for (spelling, builtin) in Builtin::ALL {
177 let name = cx.interner.intern(spelling);
178 self.macros.define_builtin(name, builtin, Span::new(start, start));
179 }
180 let text = command_line(opts);
181 if !text.is_empty() {
182 self.synthetic(COMMAND_LINE, text, cx, &names)?;
183 }
184 Ok(())
185 }
186
187 fn synthetic(
189 &mut self,
190 name: &str,
191 text: String,
192 cx: &mut Context<'_>,
193 names: &Names,
194 ) -> Result<FileId, SourceMapFull> {
195 let file = cx.sources.add(name, text.into_bytes())?;
196 let mut out = Vec::new();
197 self.stack.push(Frame { at: Span::DUMMY, path: PathBuf::from(name), dir: None, next: 0 });
201 self.process(file, &mut out, cx, names);
202 self.stack.clear();
203 debug_assert!(out.is_empty(), "{name} is directives only and produces no tokens");
204 Ok(file)
205 }
206
207 pub fn run(&mut self, file: FileId, cx: &mut Context<'_>) -> Vec<Tok> {
212 let names = Names::new(cx.interner);
213 let mut out = Vec::new();
214 let name = cx.sources.file(file).name.clone();
215 let dir = directory_of(&name);
216 self.stack.push(Frame { at: Span::DUMMY, path: PathBuf::from(name), dir, next: 0 });
219 self.process(file, &mut out, cx, &names);
220 self.stack.clear();
221 out
222 }
223
224 fn process(&mut self, file: FileId, out: &mut Vec<Tok>, cx: &mut Context<'_>, names: &Names) {
226 let bytes = cx.sources.file(file).shared_bytes();
229 let start = cx.sources.file(file).start;
230 let mut reader = Reader::new(bytes.as_slice(), start, cx.lex);
231 let depth_on_entry = self.conds.len();
232 let mut text: Vec<Tok> = Vec::new();
236 let mut body: Vec<PpToken> = Vec::new();
237 let mut scan = Scan::Start;
238
239 loop {
240 let was_live = self.live();
241 let first = reader.next(cx.interner);
242 if first.is_eof() {
243 break;
244 }
245 if is_directive(first) {
246 self.flush(&mut text, out, cx, names);
247 body.clear();
248 let name_tok = reader.next(cx.interner);
249 if name_tok.is_eof() || name_tok.flags.has(TokenFlags::START_OF_LINE) {
252 reader.put_back(name_tok);
253 continue;
254 }
255 body.push(name_tok);
256 if was_live && is_include(ident_of(&name_tok), names) {
261 if let Some(header) = reader.header_name(cx.interner) {
262 body.push(header);
263 }
264 }
265 reader.line(cx.interner, &mut body);
266 let opens =
267 matches!(scan, Scan::Start).then(|| guard_opener(&body, names)).flatten();
268 self.directive(&body, first.span, out, cx, names);
269 scan = match scan {
270 Scan::Start => match opens {
274 Some(name) if self.conds.len() == depth_on_entry + 1 => Scan::Inside(name),
275 _ => Scan::No,
276 },
277 Scan::Inside(name) if self.conds.len() == depth_on_entry => Scan::Closed(name),
278 Scan::Inside(name) => Scan::Inside(name),
279 Scan::Closed(_) | Scan::No => Scan::No,
280 };
281 } else {
282 body.clear();
283 reader.line(cx.interner, &mut body);
284 if self.live() {
285 let operator = ident_of(&first) == Some(names.pragma_op)
291 || body.iter().any(|t| ident_of(t) == Some(names.pragma_op));
292 if operator {
293 self.flush(&mut text, out, cx, names);
294 }
295 text.push(Tok::new(first));
296 text.extend(body.iter().copied().map(Tok::new));
297 if operator {
298 self.flush(&mut text, out, cx, names);
299 }
300 }
301 if !matches!(scan, Scan::Inside(_)) {
303 scan = Scan::No;
304 }
305 }
306 let complaints = reader.take_diagnostics();
309 if was_live || self.live() {
310 self.diagnostics.extend(complaints);
311 }
312 }
313 self.flush(&mut text, out, cx, names);
314 self.diagnostics.extend(reader.take_diagnostics());
315
316 if let Scan::Closed(name) = scan {
319 if self.macros.is_defined(name) {
320 if let Some(frame) = self.stack.last() {
321 self.seen.entry(frame.path.clone()).or_insert(Guard::Macro(name));
322 }
323 }
324 }
325
326 for cond in self.conds.drain(depth_on_entry..) {
329 self.diagnostics
330 .push(Diagnostic::error("unterminated `#if`", cond.span).with_code("E0330"));
331 }
332 }
333
334 fn live(&self) -> bool {
336 self.conds.last().is_none_or(|c| c.live)
337 }
338
339 fn flush(
341 &mut self,
342 text: &mut Vec<Tok>,
343 out: &mut Vec<Tok>,
344 cx: &mut Context<'_>,
345 names: &Names,
346 ) {
347 if text.is_empty() {
348 return;
349 }
350 let taken = std::mem::take(text);
351 let expanded = self.expander.expand_toks(taken, &self.macros, cx.interner, cx.sources);
352 self.diagnostics.append(&mut self.expander.take_diagnostics());
353 let expanded = self.resolve_has(expanded, cx, names, Pass::Text);
358 self.pragma_operator(expanded, out, cx.interner, names);
359 }
360
361 fn directive(
363 &mut self,
364 body: &[PpToken],
365 hash: Span,
366 out: &mut Vec<Tok>,
367 cx: &mut Context<'_>,
368 names: &Names,
369 ) {
370 let Some(first) = body.first().copied() else {
371 return;
372 };
373 let name = ident_of(&first);
374 let rest = &body[1..];
375
376 if name == Some(names.r#if) {
379 let value = self.live() && self.eval(rest, hash, cx, names);
380 self.open(hash, value);
381 return;
382 }
383 if name == Some(names.ifdef) || name == Some(names.ifndef) {
384 let want = name == Some(names.ifdef);
385 let value = self.live() && self.defined_check(rest, hash, want, names);
386 self.open(hash, value);
387 return;
388 }
389 if name == Some(names.elif) || name == Some(names.elifdef) || name == Some(names.elifndef) {
390 self.elif(name, rest, hash, cx, names);
391 return;
392 }
393 if name == Some(names.r#else) {
394 self.branch_else(rest, hash);
395 return;
396 }
397 if name == Some(names.endif) {
398 self.endif(rest, hash);
399 return;
400 }
401 if !self.live() {
402 return;
406 }
407
408 let interner = &mut *cx.interner;
409 if name == Some(names.define) {
410 let (def, diagnostics) = parse_define(rest, interner);
411 self.diagnostics.extend(diagnostics);
412 if let Some(def) = def {
413 if let Some(problem) = self.macros.define(def, interner) {
414 self.diagnostics.push(problem);
415 }
416 }
417 } else if name == Some(names.undef) {
418 self.undef(rest, hash, interner);
419 } else if name == Some(names.error) || name == Some(names.warning) {
420 self.message(rest, hash, name == Some(names.error), interner);
421 } else if name == Some(names.line) {
422 self.line(rest, hash, out.len(), cx);
423 } else if name == Some(names.pragma) {
424 if rest.len() == 1 && ident_of(&rest[0]) == Some(names.once) {
431 self.pragma_once(hash);
432 } else if !self.macro_stack_pragma(rest, hash, interner, names) {
433 self.pass_through(body, hash, out);
434 }
435 } else if name == Some(names.include) || name == Some(names.include_next) {
436 self.include(rest, hash, name == Some(names.include_next), out, cx, names);
437 } else if name == Some(names.embed) {
438 self.embed(rest, hash, out, cx);
439 } else {
440 self.diagnostics.push(
441 Diagnostic::error("invalid preprocessing directive", first.span).with_code("E0332"),
442 );
443 }
444 }
445
446 fn macro_stack_pragma(
458 &mut self,
459 rest: &[PpToken],
460 at: Span,
461 interner: &mut Interner,
462 names: &Names,
463 ) -> bool {
464 let which = match rest.first().and_then(ident_of) {
465 Some(name) if name == names.push_macro => names.push_macro,
466 Some(name) if name == names.pop_macro => names.pop_macro,
467 _ => return false,
468 };
469 let word = if which == names.push_macro { "push_macro" } else { "pop_macro" };
470 let [_, open, text, close, extra @ ..] = rest else {
475 self.invalid_pragma(word, at);
476 return true;
477 };
478 if open.punct() != Some(Punct::LParen)
479 || text.kind != PpTokenKind::StringLit
480 || close.punct() != Some(Punct::RParen)
481 {
482 self.invalid_pragma(word, at);
483 return true;
484 }
485 self.extra_tokens(extra, "#pragma");
486 let Some(name) = identifier_in(*text, interner) else {
491 return true;
492 };
493 if which == names.push_macro {
494 self.macros.push_macro(name);
495 } else {
496 self.macros.pop_macro(name);
497 }
498 true
499 }
500
501 fn invalid_pragma(&mut self, word: &str, at: Span) {
502 self.diagnostics.push(
503 Diagnostic::error(format!("invalid `#pragma {word}` directive"), at).with_code("E0672"),
504 );
505 }
506
507 fn pragma_once(&mut self, hash: Span) {
509 if self.stack.len() <= 1 {
512 self.diagnostics.push(
513 Diagnostic::warning("`#pragma once` in the main file", hash).with_code("W0332"),
514 );
515 return;
516 }
517 if let Some(frame) = self.stack.last() {
518 self.seen.insert(frame.path.clone(), Guard::Once);
519 }
520 }
521
522 fn skip(&self, path: &Path) -> bool {
524 match self.seen.get(path) {
525 Some(Guard::Once) => true,
526 Some(Guard::Macro(name)) => self.macros.is_defined(*name),
527 None => false,
528 }
529 }
530
531 fn pass_through(&mut self, body: &[PpToken], hash: Span, out: &mut Vec<Tok>) {
533 let _ = self;
534 out.push(Tok::synthetic(
535 PpTokenKind::Punct(Punct::Hash),
536 None,
537 TokenFlags::START_OF_LINE,
538 hash,
539 ));
540 for (at, token) in body.iter().copied().enumerate() {
545 let mut token = Tok::new(token);
546 if at == 0 {
547 token.flags = token.flags.without(TokenFlags::LEADING_SPACE);
548 }
549 out.push(token);
550 }
551 }
552
553 fn include(
555 &mut self,
556 rest: &[PpToken],
557 hash: Span,
558 is_next: bool,
559 out: &mut Vec<Tok>,
560 cx: &mut Context<'_>,
561 names: &Names,
562 ) {
563 let Some(header) = self.header_of(rest, hash, cx) else {
564 return;
565 };
566 let (form, relative_to, from) = self.where_to_look(&header, is_next, cx);
567 let found = cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from);
568 let Some(found) = found else {
569 let tried = cx.search.tried(&header.name, form, relative_to.as_deref(), from);
570 let where_looked = if tried.is_empty() && Path::new(&header.name).is_absolute() {
574 "the name is an absolute path, so the search path was not used".to_owned()
575 } else if tried.is_empty() {
576 "the include search path is empty".to_owned()
577 } else {
578 let list: Vec<String> =
579 tried.iter().map(|d| d.to_string_lossy().into_owned()).collect();
580 format!("searched: {}", list.join(", "))
581 };
582 self.diagnostics.push(
583 Diagnostic::error(format!("`{}` file not found", header.name), hash)
584 .with_code("E0341")
585 .note(where_looked, hash),
586 );
587 return;
588 };
589 if self.skip(&found.path) {
594 return;
595 }
596 if self.stack.len() >= cx.max_include_depth as usize {
597 let mut diagnostic =
598 Diagnostic::error("`#include` nested too deeply", hash).with_code("E0342").note(
599 "a header that includes itself with no include guard is the usual cause",
600 hash,
601 );
602 if let Some(outer) = self.stack.first().filter(|f| !f.at.is_dummy()) {
603 diagnostic = diagnostic.note("the outermost include is here", outer.at);
604 }
605 self.diagnostics.push(diagnostic);
606 return;
607 }
608 let added = cx.sources.add_shared(found.name.clone(), found.bytes.clone(), Some(hash));
609 let file = match added {
610 Ok(file) => file,
611 Err(full) => {
612 self.diagnostics.push(Diagnostic::error(full.to_string(), hash).with_code("E0344"));
613 return;
614 }
615 };
616 self.stack.push(Frame {
617 at: hash,
618 dir: found.path.parent().map(Path::to_path_buf),
619 path: found.path,
620 next: found.next,
621 });
622 self.process(file, out, cx, names);
623 self.stack.pop();
624 }
625
626 fn embed(&mut self, rest: &[PpToken], hash: Span, out: &mut Vec<Tok>, cx: &mut Context<'_>) {
628 let Some((header, params)) = self.embed_line(rest, hash, cx) else {
629 return;
630 };
631 let Some(found) = self.find(&header, false, cx) else {
632 self.diagnostics.push(
633 Diagnostic::error(format!("`{}` resource not found", header.name), hash)
634 .with_code("E0341")
635 .note("an `#embed` resource is looked for on the include path", hash),
636 );
637 return;
638 };
639 embed::tokens(found.bytes.as_slice(), ¶ms, hash, cx.interner, out);
644 }
645
646 fn embed_line(
648 &mut self,
649 rest: &[PpToken],
650 hash: Span,
651 cx: &mut Context<'_>,
652 ) -> Option<(Header, embed::Params)> {
653 if rest.is_empty() {
654 self.bad_header(hash);
655 return None;
656 }
657 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
658 let line = if line[0].kind == PpTokenKind::HeaderName {
664 line
665 } else {
666 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
667 self.diagnostics.append(&mut self.expander.take_diagnostics());
668 expanded
669 };
670 let Some(used) = embed::header_length(&line) else {
671 self.bad_header(line.first().map_or(hash, |t| t.report_span()));
672 return None;
673 };
674 let header = if line[0].kind == PpTokenKind::HeaderName {
675 header_from_token(spelling(line[0], cx.interner))
676 } else {
677 let spellings: Vec<&str> =
678 line[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
679 header_from_tokens(&spellings)
680 };
681 let Some(header) = header else {
682 self.bad_header(line[0].report_span());
683 return None;
684 };
685 let params = self.embed_params(&line[used..], hash, cx)?;
686 Some((header, params))
687 }
688
689 fn embed_params(
691 &mut self,
692 line: &[Tok],
693 at: Span,
694 cx: &mut Context<'_>,
695 ) -> Option<embed::Params> {
696 let Preprocessor { expander, macros, diagnostics, .. } = self;
697 let sources = &mut *cx.sources;
698 let mut expand = |toks: Vec<Tok>, interner: &mut Interner| {
699 expander.expand_toks(toks, macros, interner, sources)
700 };
701 let params = embed::parse(line, at, cx.interner, diagnostics, &mut expand);
702 self.diagnostics.append(&mut self.expander.take_diagnostics());
703 params
704 }
705
706 fn where_to_look(
717 &self,
718 header: &Header,
719 is_next: bool,
720 cx: &Context<'_>,
721 ) -> (IncludeForm, Option<PathBuf>, usize) {
722 let form = if header.angled { IncludeForm::Angled } else { IncludeForm::Quoted };
723 let frame = self.stack.last();
724 let from = if is_next {
725 frame.map_or(0, |f| f.next).max(cx.search.start(form))
726 } else {
727 cx.search.start(form)
728 };
729 let relative_to = if is_next { None } else { frame.and_then(|f| f.dir.clone()) };
730 (form, relative_to, from)
731 }
732
733 fn find(&self, header: &Header, is_next: bool, cx: &Context<'_>) -> Option<Found> {
735 let (form, relative_to, from) = self.where_to_look(header, is_next, cx);
736 cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from)
737 }
738
739 fn header_of(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>) -> Option<Header> {
741 if let Some(first) = rest.first().copied() {
742 if first.kind == PpTokenKind::HeaderName {
743 let text = first.value.map_or("", |v| cx.interner.resolve(v));
744 let header = header_from_token(text);
745 if header.is_none() {
746 self.bad_header(first.span);
747 }
748 self.extra_tokens(&rest[1..], "#include");
749 return header;
750 }
751 }
752 if rest.is_empty() {
756 self.bad_header(hash);
757 return None;
758 }
759 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
760 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
761 self.diagnostics.append(&mut self.expander.take_diagnostics());
762 let spellings: Vec<&str> = expanded.iter().map(|t| spelling(*t, cx.interner)).collect();
763 let header = header_from_tokens(&spellings);
764 if header.is_none() {
765 let at = expanded.first().map_or(hash, |t| t.report_span());
766 self.bad_header(at);
767 }
768 header
769 }
770
771 fn bad_operand(&mut self, tok: Tok, at: Span, interner: &Interner) {
773 self.diagnostics.push(
774 Diagnostic::error(
775 format!("expected an identifier as the operand of `{}`", spelling(tok, interner)),
776 at,
777 )
778 .with_code("E0345"),
779 );
780 }
781
782 fn bad_header(&mut self, at: Span) {
783 self.diagnostics.push(
784 Diagnostic::error("expected a file name in `<>` or `\"\"`", at).with_code("E0343"),
785 );
786 }
787
788 fn open(&mut self, span: Span, value: bool) {
790 let enclosing_live = self.live();
791 self.conds.push(Cond {
792 span,
793 live: enclosing_live && value,
794 taken: value,
795 enclosing_live,
796 seen_else: false,
797 });
798 }
799
800 fn elif(
801 &mut self,
802 name: Option<Symbol>,
803 rest: &[PpToken],
804 hash: Span,
805 cx: &mut Context<'_>,
806 names: &Names,
807 ) {
808 let Some(top) = self.conds.last() else {
809 self.stray("elif", hash);
810 return;
811 };
812 if top.seen_else {
813 self.diagnostics
814 .push(Diagnostic::error("`#elif` after `#else`", hash).with_code("E0333"));
815 return;
816 }
817 let (enclosing_live, already_taken) = (top.enclosing_live, top.taken);
820 let consider = enclosing_live && !already_taken;
821 let value = if !consider {
822 false
823 } else if name == Some(names.elif) {
824 self.eval(rest, hash, cx, names)
825 } else {
826 self.defined_check(rest, hash, name == Some(names.elifdef), names)
827 };
828 let top = self.conds.last_mut().expect("checked above and nothing popped");
829 top.live = consider && value;
830 top.taken = already_taken || value;
831 }
832
833 fn branch_else(&mut self, rest: &[PpToken], hash: Span) {
834 let Some(top) = self.conds.last_mut() else {
835 self.stray("else", hash);
836 return;
837 };
838 if top.seen_else {
839 self.diagnostics.push(Diagnostic::error("a second `#else`", hash).with_code("E0333"));
840 return;
841 }
842 top.live = top.enclosing_live && !top.taken;
843 top.taken = true;
844 top.seen_else = true;
845 let enclosing_live = top.enclosing_live;
846 if enclosing_live {
847 self.extra_tokens(rest, "#else");
848 }
849 }
850
851 fn endif(&mut self, rest: &[PpToken], hash: Span) {
852 if self.conds.pop().is_none() {
853 self.stray("endif", hash);
854 return;
855 }
856 if self.live() {
857 self.extra_tokens(rest, "#endif");
858 }
859 }
860
861 fn stray(&mut self, what: &str, hash: Span) {
862 self.diagnostics
863 .push(Diagnostic::error(format!("`#{what}` without `#if`"), hash).with_code("E0334"));
864 }
865
866 fn extra_tokens(&mut self, rest: &[PpToken], what: &str) {
871 if let Some(first) = rest.first() {
872 self.diagnostics.push(
873 Diagnostic::warning(format!("extra tokens after `{what}`"), first.span)
874 .with_code("W0330"),
875 );
876 }
877 }
878
879 fn eval(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>, names: &Names) -> bool {
881 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
882 let line = self.resolve_defined(line, cx.interner, names);
888 let line = self.resolve_has(line, cx, names, Pass::Headers);
893 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
894 self.diagnostics.append(&mut self.expander.take_diagnostics());
895 let line = self.resolve_defined(line, cx.interner, names);
896 let line = self.resolve_has(line, cx, names, Pass::Rest);
897 cond::evaluate(&line, cx.interner, &mut self.diagnostics, hash)
898 }
899
900 fn resolve_has(
905 &mut self,
906 line: Vec<Tok>,
907 cx: &mut Context<'_>,
908 names: &Names,
909 pass: Pass,
910 ) -> Vec<Tok> {
911 if !line.iter().any(|t| t.ident().is_some_and(|n| names.has.op(n).is_some())) {
912 return line;
913 }
914 let mut out = Vec::with_capacity(line.len());
915 let mut at = 0;
916 while at < line.len() {
917 let tok = line[at];
918 let op = tok.ident().and_then(|n| names.has.op(n));
919 let Some(op) = op.filter(|op| pass.answers(*op)) else {
920 if pass == Pass::Text && op.is_some_and(Op::is_header) {
921 self.outside_a_directive(tok, cx);
922 }
923 out.push(tok);
924 at += 1;
925 continue;
926 };
927 let Some((operand, after)) = arguments(&line, at + 1) else {
928 if pass != Pass::Headers {
932 self.diagnostics.push(
933 Diagnostic::error(
934 format!("expected `(` after `{}`", spelling(tok, cx.interner)),
935 tok.report_span(),
936 )
937 .with_code("E0345"),
938 );
939 }
940 out.push(tok);
941 at += 1;
942 continue;
943 };
944 at = after;
945 let value = self.ask(op, operand, tok, cx);
948 let sym = cx.interner.intern(&value.to_string());
949 out.push(Tok::synthetic(PpTokenKind::Number, Some(sym), tok.flags, tok.report_span()));
950 }
951 out
952 }
953
954 fn outside_a_directive(&mut self, tok: Tok, cx: &Context<'_>) {
962 self.diagnostics.push(
963 Diagnostic::error(
964 format!(
965 "`{}` used outside of a preprocessing directive",
966 spelling(tok, cx.interner)
967 ),
968 tok.report_span(),
969 )
970 .with_code("E0350"),
971 );
972 }
973
974 fn ask(&mut self, op: Op, operand: &[Tok], tok: Tok, cx: &mut Context<'_>) -> u32 {
976 let at = operand.first().map_or(tok.report_span(), |t| t.report_span());
977 match op {
978 Op::Include | Op::IncludeNext => {
979 let spellings: Vec<&str> =
980 operand.iter().map(|t| spelling(*t, cx.interner)).collect();
981 let Some(header) = header_from_tokens(&spellings) else {
982 self.bad_header(at);
983 return 0;
984 };
985 u32::from(self.find(&header, op == Op::IncludeNext, cx).is_some())
986 }
987 Op::Embed => {
988 let Some(used) = embed::header_length(operand) else {
993 self.bad_header(at);
994 return 0;
995 };
996 let header = if operand[0].kind == PpTokenKind::HeaderName {
997 header_from_token(spelling(operand[0], cx.interner))
998 } else {
999 let spellings: Vec<&str> =
1000 operand[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
1001 header_from_tokens(&spellings)
1002 };
1003 let Some(header) = header else {
1004 self.bad_header(at);
1005 return 0;
1006 };
1007 let Some(params) = self.embed_params(&operand[used..], at, cx) else {
1012 return 0;
1013 };
1014 match self.find(&header, false, cx) {
1015 None => 0,
1016 Some(found) => {
1017 let taken = params.taken(found.bytes.as_slice().len() as u64);
1018 if taken == 0 { 2 } else { 1 }
1019 }
1020 }
1021 }
1022 Op::BuildingModule => {
1023 if attribute_name(operand, cx.interner).is_none() {
1024 self.bad_operand(tok, at, cx.interner);
1025 }
1026 0
1033 }
1034 Op::Table(kind) => {
1035 let Some(name) = attribute_name(operand, cx.interner) else {
1036 self.bad_operand(tok, at, cx.interner);
1037 return 0;
1038 };
1039 match kind {
1040 Kind::Attribute => rucc_gnu::has_attribute(name),
1041 Kind::CAttribute => rucc_gnu::has_c_attribute(name),
1042 Kind::Builtin => rucc_gnu::has_builtin(name),
1043 Kind::Feature => rucc_gnu::has_feature(name),
1044 Kind::Extension => rucc_gnu::has_extension(name),
1045 }
1046 }
1047 }
1048 }
1049
1050 fn resolve_defined(
1052 &mut self,
1053 line: Vec<Tok>,
1054 interner: &mut Interner,
1055 names: &Names,
1056 ) -> Vec<Tok> {
1057 if !line.iter().any(|t| t.ident() == Some(names.defined)) {
1058 return line;
1059 }
1060 let mut out = Vec::with_capacity(line.len());
1061 let mut at = 0;
1062 while at < line.len() {
1063 let tok = line[at];
1064 if tok.ident() != Some(names.defined) {
1065 out.push(tok);
1066 at += 1;
1067 continue;
1068 }
1069 let parenthesised = line.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1070 let name_at = if parenthesised { at + 2 } else { at + 1 };
1071 let name = line.get(name_at).and_then(|t| t.ident());
1072 let Some(name) = name else {
1073 self.diagnostics.push(
1074 Diagnostic::error("`defined` without a macro name", tok.report_span())
1075 .with_code("E0335"),
1076 );
1077 out.push(tok);
1078 at += 1;
1079 continue;
1080 };
1081 at = name_at + 1;
1082 if parenthesised {
1083 if line.get(at).is_some_and(|t| t.is(Punct::RParen)) {
1084 at += 1;
1085 } else {
1086 self.diagnostics.push(
1087 Diagnostic::error("expected `)` after `defined`", tok.report_span())
1088 .with_code("E0335"),
1089 );
1090 }
1091 }
1092 let value = self.macros.is_defined(name) || names.has.op(name).is_some();
1096 out.push(number(value, tok.flags, tok.report_span(), interner));
1097 }
1098 out
1099 }
1100
1101 fn defined_check(
1103 &mut self,
1104 rest: &[PpToken],
1105 hash: Span,
1106 want_defined: bool,
1107 names: &Names,
1108 ) -> bool {
1109 let Some(name) = rest.first().and_then(ident_of) else {
1110 self.diagnostics.push(
1111 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1112 .with_code("E0336"),
1113 );
1114 return false;
1115 };
1116 self.extra_tokens(&rest[1..], if want_defined { "#ifdef" } else { "#ifndef" });
1117 let defined = self.macros.is_defined(name) || names.has.op(name).is_some();
1118 defined == want_defined
1119 }
1120
1121 fn undef(&mut self, rest: &[PpToken], hash: Span, interner: &Interner) {
1122 let Some(name) = rest.first().and_then(ident_of) else {
1123 self.diagnostics.push(
1124 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1125 .with_code("E0336"),
1126 );
1127 return;
1128 };
1129 let text = interner.resolve(name);
1132 if text == "defined" || text.starts_with("__STDC_") {
1133 self.diagnostics.push(
1134 Diagnostic::error(format!("`{text}` cannot be undefined"), rest[0].span)
1135 .with_code("E0337"),
1136 );
1137 return;
1138 }
1139 self.macros.undef(name);
1140 self.extra_tokens(&rest[1..], "#undef");
1141 }
1142
1143 fn message(&mut self, rest: &[PpToken], hash: Span, fatal: bool, interner: &Interner) {
1145 let text = spell_line(rest, interner);
1146 let span = rest.first().map_or(hash, |t| t.span.to(last_span(rest)));
1147 let diag = if fatal {
1148 Diagnostic::error(text, span).with_code("E0338")
1149 } else {
1150 Diagnostic::warning(text, span).with_code("W0331")
1151 };
1152 self.diagnostics.push(diag);
1153 }
1154
1155 fn line(&mut self, rest: &[PpToken], hash: Span, at: usize, cx: &mut Context<'_>) {
1160 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
1161 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
1162 self.diagnostics.append(&mut self.expander.take_diagnostics());
1163 let interner = &mut *cx.interner;
1164
1165 let number_text = line
1166 .first()
1167 .filter(|t| t.kind == PpTokenKind::Number)
1168 .and_then(|t| t.value)
1169 .map(|v| interner.resolve(v));
1170 let Some(parsed) = number_text.and_then(|t| t.parse::<u64>().ok()) else {
1171 self.diagnostics.push(
1172 Diagnostic::error(
1173 "`#line` needs a decimal line number",
1174 line.first().map_or(hash, |t| t.report_span()),
1175 )
1176 .with_code("E0339"),
1177 );
1178 return;
1179 };
1180 if parsed == 0 || parsed > 2_147_483_647 {
1183 self.diagnostics.push(
1184 Diagnostic::error("`#line` number is out of range", line[0].report_span())
1185 .with_code("E0339"),
1186 );
1187 return;
1188 }
1189
1190 let mut file = None;
1191 if let Some(second) = line.get(1) {
1192 if second.kind == PpTokenKind::StringLit {
1193 file = second.value;
1194 } else {
1195 self.diagnostics.push(
1196 Diagnostic::error(
1197 "`#line` file name must be a string literal",
1198 second.report_span(),
1199 )
1200 .with_code("E0339"),
1201 );
1202 return;
1203 }
1204 }
1205 if let Some(extra) = line.get(2) {
1206 self.diagnostics.push(
1207 Diagnostic::warning("extra tokens after `#line`", extra.report_span())
1208 .with_code("W0330"),
1209 );
1210 }
1211 #[expect(
1212 clippy::cast_possible_truncation,
1213 reason = "the range check above keeps this inside i32, let alone u32"
1214 )]
1215 let number = parsed as u32;
1216 self.lines.push(LineDirective { span: hash, line: number, file, at });
1217 let name = file.map(|v| destringize(cx.interner.resolve(v)));
1218 cx.sources.set_presumed(hash.lo, number, name);
1219 }
1220
1221 fn pragma_operator(
1227 &mut self,
1228 expanded: Vec<Tok>,
1229 out: &mut Vec<Tok>,
1230 interner: &mut Interner,
1231 names: &Names,
1232 ) {
1233 if !expanded.iter().any(|t| t.ident() == Some(names.pragma_op)) {
1234 out.extend(expanded);
1235 return;
1236 }
1237 let mut at = 0;
1238 let mut ends_a_line = false;
1243 while at < expanded.len() {
1244 let mut tok = expanded[at];
1245 if tok.ident() != Some(names.pragma_op) {
1246 if ends_a_line {
1247 tok.flags = tok.flags.with(TokenFlags::START_OF_LINE);
1248 ends_a_line = false;
1249 }
1250 out.push(tok);
1251 at += 1;
1252 continue;
1253 }
1254 let open = expanded.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1255 let text = expanded.get(at + 2).filter(|t| t.kind == PpTokenKind::StringLit);
1256 let close = expanded.get(at + 3).is_some_and(|t| t.is(Punct::RParen));
1257 let (Some(text), true, true) = (text, open, close) else {
1258 self.diagnostics.push(
1259 Diagnostic::error("`_Pragma` takes a single string literal", tok.report_span())
1260 .with_code("E0340"),
1261 );
1262 out.push(tok);
1263 at += 1;
1264 continue;
1265 };
1266 let literal = text.value.map(|v| interner.resolve(v)).unwrap_or_default();
1267 let body = destringize(literal);
1268 self.emit_pragma(&body, tok, out, interner, names);
1269 ends_a_line = true;
1270 at += 4;
1271 }
1272 }
1273
1274 fn emit_pragma(
1276 &mut self,
1277 body: &str,
1278 at: Tok,
1279 out: &mut Vec<Tok>,
1280 interner: &mut Interner,
1281 names: &Names,
1282 ) {
1283 let span = at.report_span();
1284 let (tokens, diagnostics) = tokenize(body.as_bytes(), 0, Options::new(), interner);
1285 self.diagnostics.extend(
1288 diagnostics
1289 .into_iter()
1290 .map(|d| Diagnostic::new(d.severity, d.message, span).with_code("E0340")),
1291 );
1292 let tokens: Vec<PpToken> = tokens.into_iter().filter(|t| !t.is_eof()).collect();
1293 if self.macro_stack_pragma(&tokens, span, interner, names) {
1297 return;
1298 }
1299 out.push(Tok::synthetic(
1300 PpTokenKind::Punct(Punct::Hash),
1301 None,
1302 TokenFlags::START_OF_LINE,
1303 span,
1304 ));
1305 out.push(Tok::synthetic(PpTokenKind::Ident, Some(names.pragma), TokenFlags::EMPTY, span));
1306 for (at, t) in tokens.into_iter().enumerate() {
1310 let spaced = at == 0 || t.flags.has(TokenFlags::LEADING_SPACE);
1313 let flags = if spaced {
1314 TokenFlags::EMPTY.with(TokenFlags::LEADING_SPACE)
1315 } else {
1316 TokenFlags::EMPTY
1317 };
1318 out.push(Tok::synthetic(t.kind, t.value, flags, span));
1319 }
1320 }
1321}
1322
1323fn guard_opener(body: &[PpToken], names: &Names) -> Option<Symbol> {
1328 let name = ident_of(body.first()?)?;
1329 let rest = &body[1..];
1330 if name == names.ifndef {
1331 let [only] = rest else {
1332 return None;
1333 };
1334 return ident_of(only);
1335 }
1336 if name != names.r#if {
1337 return None;
1338 }
1339 let [bang, defined, tail @ ..] = rest else {
1340 return None;
1341 };
1342 if bang.punct() != Some(Punct::Bang) || ident_of(defined) != Some(names.defined) {
1343 return None;
1344 }
1345 match tail {
1346 [only] => ident_of(only),
1347 [open, only, close]
1348 if open.punct() == Some(Punct::LParen) && close.punct() == Some(Punct::RParen) =>
1349 {
1350 ident_of(only)
1351 }
1352 _ => None,
1353 }
1354}
1355
1356fn is_include(name: Option<Symbol>, names: &Names) -> bool {
1358 name == Some(names.include) || name == Some(names.include_next) || name == Some(names.embed)
1359}
1360
1361fn is_directive(tok: PpToken) -> bool {
1363 tok.flags.has(TokenFlags::START_OF_LINE) && tok.punct() == Some(Punct::Hash)
1364}
1365
1366fn ident_of(tok: &PpToken) -> Option<Symbol> {
1367 match tok.kind {
1368 PpTokenKind::Ident => tok.value,
1369 _ => None,
1370 }
1371}
1372
1373fn last_span(tokens: &[PpToken]) -> Span {
1374 tokens.last().map_or(Span::DUMMY, |t| t.span)
1375}
1376
1377fn number(value: bool, flags: TokenFlags, span: Span, interner: &mut Interner) -> Tok {
1379 let sym = interner.intern(if value { "1" } else { "0" });
1380 Tok::synthetic(PpTokenKind::Number, Some(sym), flags, span)
1381}
1382
1383fn spell_line(tokens: &[PpToken], interner: &Interner) -> String {
1385 let mut out = String::new();
1386 for (index, tok) in tokens.iter().enumerate() {
1387 if index > 0 && tok.flags.has(TokenFlags::LEADING_SPACE) {
1388 out.push(' ');
1389 }
1390 match tok.value {
1391 Some(sym) => out.push_str(interner.resolve(sym)),
1392 None => {
1393 if let Some(p) = tok.punct() {
1394 out.push_str(p.as_str());
1395 }
1396 }
1397 }
1398 }
1399 out
1400}
1401
1402fn identifier_in(text: PpToken, interner: &mut Interner) -> Option<Symbol> {
1407 let literal = interner.resolve(text.value?).to_string();
1408 let (tokens, _) = tokenize(destringize(&literal).as_bytes(), 0, Options::new(), interner);
1409 let mut real = tokens.into_iter().filter(|t| !t.is_eof());
1410 let first = real.next()?;
1411 if first.kind != PpTokenKind::Ident || real.next().is_some() {
1412 return None;
1413 }
1414 first.value
1415}
1416
1417fn destringize(literal: &str) -> String {
1422 let body = literal
1423 .trim_start_matches(['L', 'u', 'U', '8'])
1424 .strip_prefix('"')
1425 .and_then(|s| s.strip_suffix('"'))
1426 .unwrap_or(literal);
1427 let mut out = String::with_capacity(body.len());
1428 let mut chars = body.chars();
1429 while let Some(c) = chars.next() {
1430 if c != '\\' {
1431 out.push(c);
1432 continue;
1433 }
1434 match chars.next() {
1435 Some('"') => out.push('"'),
1436 Some('\\') => out.push('\\'),
1437 Some(other) => {
1438 out.push('\\');
1439 out.push(other);
1440 }
1441 None => out.push('\\'),
1442 }
1443 }
1444 out
1445}
1446
1447fn arguments(line: &[Tok], at: usize) -> Option<(&[Tok], usize)> {
1453 if !line.get(at)?.is(Punct::LParen) {
1454 return None;
1455 }
1456 let mut depth = 1u32;
1457 let mut end = at + 1;
1458 while end < line.len() {
1459 if line[end].is(Punct::LParen) {
1460 depth += 1;
1461 } else if line[end].is(Punct::RParen) {
1462 depth -= 1;
1463 if depth == 0 {
1464 return Some((&line[at + 1..end], end + 1));
1465 }
1466 }
1467 end += 1;
1468 }
1469 None
1470}
1471
1472fn attribute_name<'i>(operand: &[Tok], interner: &'i Interner) -> Option<&'i str> {
1478 let name = match operand {
1479 [one] => one,
1480 [_, scope, name] if scope.is(Punct::ColonColon) => name,
1481 _ => return None,
1482 };
1483 name.ident().map(|sym| interner.resolve(sym))
1484}
1485
1486#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1493enum Pass {
1494 Headers,
1496 Rest,
1499 Text,
1501}
1502
1503impl Pass {
1504 fn answers(self, op: Op) -> bool {
1506 match self {
1507 Pass::Headers => op.is_header(),
1508 Pass::Rest => true,
1509 Pass::Text => !op.is_header(),
1510 }
1511 }
1512}
1513
1514#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1516enum Op {
1517 Include,
1519 IncludeNext,
1521 Embed,
1524 BuildingModule,
1526 Table(Kind),
1528}
1529
1530impl Op {
1531 fn is_header(self) -> bool {
1533 matches!(self, Op::Include | Op::IncludeNext | Op::Embed)
1534 }
1535}
1536
1537struct HasOps {
1542 ops: [(Symbol, Op); 9],
1543 range: (Symbol, Symbol),
1550}
1551
1552impl HasOps {
1553 fn new(interner: &mut Interner) -> HasOps {
1554 let ops = [
1555 (interner.intern("__has_include"), Op::Include),
1556 (interner.intern("__has_include_next"), Op::IncludeNext),
1557 (interner.intern("__has_embed"), Op::Embed),
1558 (interner.intern("__has_attribute"), Op::Table(Kind::Attribute)),
1559 (interner.intern("__has_c_attribute"), Op::Table(Kind::CAttribute)),
1560 (interner.intern("__has_builtin"), Op::Table(Kind::Builtin)),
1561 (interner.intern("__has_feature"), Op::Table(Kind::Feature)),
1562 (interner.intern("__has_extension"), Op::Table(Kind::Extension)),
1563 (interner.intern("__building_module"), Op::BuildingModule),
1564 ];
1565 let mut range = (ops[0].0, ops[0].0);
1566 for &(sym, _) in &ops {
1567 range = (range.0.min(sym), range.1.max(sym));
1568 }
1569 HasOps { ops, range }
1570 }
1571
1572 #[inline]
1574 fn op(&self, name: Symbol) -> Option<Op> {
1575 if name < self.range.0 || name > self.range.1 {
1576 return None;
1577 }
1578 self.ops.iter().find(|(sym, _)| *sym == name).map(|(_, op)| *op)
1579 }
1580}
1581
1582struct Names {
1588 define: Symbol,
1589 undef: Symbol,
1590 r#if: Symbol,
1591 ifdef: Symbol,
1592 ifndef: Symbol,
1593 elif: Symbol,
1594 elifdef: Symbol,
1595 elifndef: Symbol,
1596 r#else: Symbol,
1597 endif: Symbol,
1598 line: Symbol,
1599 error: Symbol,
1600 warning: Symbol,
1601 pragma: Symbol,
1602 include: Symbol,
1603 include_next: Symbol,
1604 embed: Symbol,
1605 defined: Symbol,
1606 once: Symbol,
1607 push_macro: Symbol,
1608 pop_macro: Symbol,
1609 pragma_op: Symbol,
1610 has: HasOps,
1611}
1612
1613impl Names {
1614 fn new(interner: &mut Interner) -> Names {
1615 Names {
1616 define: interner.intern("define"),
1617 undef: interner.intern("undef"),
1618 r#if: interner.intern("if"),
1619 ifdef: interner.intern("ifdef"),
1620 ifndef: interner.intern("ifndef"),
1621 elif: interner.intern("elif"),
1622 elifdef: interner.intern("elifdef"),
1623 elifndef: interner.intern("elifndef"),
1624 r#else: interner.intern("else"),
1625 endif: interner.intern("endif"),
1626 line: interner.intern("line"),
1627 error: interner.intern("error"),
1628 warning: interner.intern("warning"),
1629 pragma: interner.intern("pragma"),
1630 include: interner.intern("include"),
1631 include_next: interner.intern("include_next"),
1632 embed: interner.intern("embed"),
1633 defined: interner.intern("defined"),
1634 once: interner.intern("once"),
1635 push_macro: interner.intern("push_macro"),
1636 pop_macro: interner.intern("pop_macro"),
1637 pragma_op: interner.intern("_Pragma"),
1638 has: HasOps::new(interner),
1639 }
1640 }
1641}
1642
1643#[cfg(test)]
1644mod tests {
1645 use rucc_diag::{Severity, SourceMap};
1646 use rucc_session::{MemoryFileSystem, SearchPath};
1647
1648 use super::*;
1649 use rucc_session::Std;
1650
1651 use crate::predef::Timestamp;
1652
1653 struct Run {
1658 interner: Interner,
1659 sources: SourceMap,
1660 fs: MemoryFileSystem,
1661 search: SearchPath,
1662 pp: Preprocessor,
1663 }
1664
1665 impl Run {
1666 fn new() -> Run {
1667 Run {
1668 interner: Interner::new(),
1669 sources: SourceMap::new(),
1670 fs: MemoryFileSystem::new(),
1671 search: SearchPath::new(),
1672 pp: Preprocessor::new(),
1673 }
1674 }
1675
1676 fn file(&mut self, path: &str, contents: &str) {
1678 self.fs.insert(path, contents.as_bytes().to_vec());
1679 }
1680
1681 fn bytes(&mut self, path: &str, contents: &[u8]) {
1684 self.fs.insert(path, contents.to_vec());
1685 }
1686
1687 fn dir(&mut self, path: &str) {
1689 self.search.push_bracket(path);
1690 }
1691
1692 fn predefine(&mut self, triple: &str, opts: &Predef) {
1694 let target = TargetInfo::new(triple.parse().expect("a supported triple"));
1695 let mut cx =
1696 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1697 self.pp.predefine(&target, opts, &mut cx).expect("the map has room");
1698 }
1699
1700 fn go(&mut self, src: &str) -> String {
1702 self.go_named("/main.c", src)
1703 }
1704
1705 fn raw(&mut self, src: &str) -> Vec<Tok> {
1707 let file = self.sources.add("/main.c", src.as_bytes().to_vec()).expect("room");
1708 let mut cx =
1709 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1710 self.pp.run(file, &mut cx)
1711 }
1712
1713 fn go_named(&mut self, path: &str, src: &str) -> String {
1715 let file = self.sources.add(path, src.as_bytes().to_vec()).expect("the map has room");
1716 let out = {
1717 let mut cx =
1718 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1719 self.pp.run(file, &mut cx)
1720 };
1721 let mut text = String::new();
1722 for (at, tok) in out.iter().enumerate() {
1723 let spaced = tok.flags.has(TokenFlags::LEADING_SPACE)
1724 || tok.flags.has(TokenFlags::START_OF_LINE);
1725 if at > 0 && spaced {
1726 text.push(' ');
1727 }
1728 match tok.kind {
1729 PpTokenKind::Punct(p) => text.push_str(p.as_str()),
1730 _ => text.push_str(
1731 self.interner.resolve(tok.value.expect("every non-punctuator interns")),
1732 ),
1733 }
1734 }
1735 text
1736 }
1737
1738 fn files(&self) -> usize {
1742 self.sources.files().len()
1743 }
1744
1745 fn messages(&mut self) -> Vec<String> {
1746 self.pp.take_diagnostics().into_iter().map(|d| d.message).collect()
1747 }
1748
1749 fn severities(&mut self) -> Vec<Severity> {
1750 self.pp.diagnostics().iter().map(|d| d.severity).collect()
1751 }
1752 }
1753
1754 fn clean(src: &str) -> String {
1755 let mut run = Run::new();
1756 let text = run.go(src);
1757 assert!(run.messages().is_empty(), "expected no diagnostics from {src:?}");
1758 text
1759 }
1760
1761 #[test]
1762 fn a_taken_branch_is_kept_and_the_other_is_not() {
1763 assert_eq!(clean("#if 1\nyes\n#else\nno\n#endif\n"), "yes");
1764 assert_eq!(clean("#if 0\nyes\n#else\nno\n#endif\n"), "no");
1765 }
1766
1767 #[test]
1768 fn ifdef_and_ifndef_ask_the_macro_table() {
1769 assert_eq!(clean("#define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
1770 assert_eq!(clean("#ifdef F\nyes\n#endif\n"), "");
1771 assert_eq!(clean("#ifndef F\nyes\n#endif\n"), "yes");
1772 assert_eq!(clean("#define F 1\n#if 0\na\n#elifdef F\nb\n#endif\n"), "b");
1774 assert_eq!(clean("#if 0\na\n#elifndef F\nb\n#endif\n"), "b");
1775 }
1776
1777 #[test]
1778 fn only_the_first_true_branch_of_a_chain_is_taken() {
1779 assert_eq!(clean("#if 0\na\n#elif 1\nb\n#elif 1\nc\n#else\nd\n#endif\n"), "b");
1780 assert_eq!(clean("#if 0\na\n#elif 0\nb\n#else\nc\n#endif\n"), "c");
1781 }
1782
1783 #[test]
1784 fn a_branch_after_one_that_was_taken_is_not_evaluated() {
1785 assert_eq!(clean("#if 1\na\n#elif 1/0\nb\n#endif\n"), "a");
1788 }
1789
1790 #[test]
1791 fn a_skipped_region_is_not_read_for_anything_but_nesting() {
1792 let src = "#if 0\nthis is not C at all\n#frobnicate\n#define\n#if 1\ninner\n#endif\n#endif\nafter\n";
1794 assert_eq!(clean(src), "after");
1795 }
1796
1797 #[test]
1798 fn nesting_inside_a_dead_branch_stays_balanced() {
1799 let src = "#if 0\n#ifdef X\na\n#else\nb\n#endif\n#else\nc\n#endif\n";
1800 assert_eq!(clean(src), "c");
1801 }
1802
1803 #[test]
1804 fn defined_works_in_both_spellings_and_before_expansion() {
1805 assert_eq!(clean("#define F 0\n#if defined F\nyes\n#endif\n"), "yes");
1806 assert_eq!(clean("#define F 0\n#if defined(F)\nyes\n#endif\n"), "yes");
1807 assert_eq!(clean("#if defined(F)\nyes\n#endif\n"), "");
1808 assert_eq!(clean("#define F 0\n#if defined F && !F\nyes\n#endif\n"), "yes");
1811 }
1812
1813 #[test]
1814 fn an_identifier_that_survived_expansion_is_zero() {
1815 assert_eq!(clean("#if NOT_DEFINED_ANYWHERE\nyes\n#else\nno\n#endif\n"), "no");
1816 assert_eq!(clean("#if !NOT_DEFINED_ANYWHERE\nyes\n#endif\n"), "yes");
1817 }
1818
1819 #[test]
1820 fn short_circuiting_keeps_a_guarded_expression_safe() {
1821 assert_eq!(clean("#if defined(F) && 1/F\nyes\n#else\nno\n#endif\n"), "no");
1824 assert_eq!(clean("#if 1 ? 2 : 1/0\nyes\n#endif\n"), "yes");
1825 }
1826
1827 #[test]
1828 fn the_operators_have_the_precedence_they_do_in_c() {
1829 assert_eq!(clean("#if 1 + 2 * 3 == 7\nyes\n#endif\n"), "yes");
1830 assert_eq!(clean("#if (1 + 2) * 3 == 9\nyes\n#endif\n"), "yes");
1831 assert_eq!(clean("#if 1 << 4 == 16\nyes\n#endif\n"), "yes");
1832 assert_eq!(clean("#if -8 / 3 == -2\nyes\n#endif\n"), "yes");
1833 assert_eq!(clean("#if (0xff & 0x0f) == 15\nyes\n#endif\n"), "yes");
1834 }
1835
1836 #[test]
1837 fn an_unsigned_operand_makes_the_whole_comparison_unsigned() {
1838 assert_eq!(clean("#if -1 < 0u\nyes\n#else\nno\n#endif\n"), "no");
1842 assert_eq!(clean("#if -1 < 0\nyes\n#else\nno\n#endif\n"), "yes");
1843 }
1844
1845 #[test]
1846 fn character_constants_evaluate() {
1847 assert_eq!(clean("#if 'A' == 65\nyes\n#endif\n"), "yes");
1848 assert_eq!(clean("#if '\\n' == 10\nyes\n#endif\n"), "yes");
1849 }
1850
1851 #[test]
1852 fn a_macro_is_expanded_before_the_expression_is_evaluated() {
1853 assert_eq!(clean("#define V 3\n#if V > 2\nyes\n#endif\n"), "yes");
1854 assert_eq!(clean("#define M(a) ((a) * 2)\n#if M(3) == 6\nyes\n#endif\n"), "yes");
1855 }
1856
1857 #[test]
1858 fn an_invocation_may_span_lines_within_a_run_of_text() {
1859 assert_eq!(clean("#define M(a, b) a + b\nM(1,\n2)\n"), "1 + 2");
1860 }
1861
1862 #[test]
1863 fn undef_removes_a_definition() {
1864 assert_eq!(clean("#define F 1\n#undef F\n#ifdef F\nyes\n#else\nno\n#endif\n"), "no");
1865 assert_eq!(clean("#undef NEVER_DEFINED\nok\n"), "ok");
1868 }
1869
1870 #[test]
1871 fn some_names_cannot_be_undefined() {
1872 let mut run = Run::new();
1873 run.go("#undef defined\n");
1874 assert_eq!(run.messages(), vec!["`defined` cannot be undefined".to_owned()]);
1875 }
1876
1877 #[test]
1878 fn error_reports_the_rest_of_the_line() {
1879 let mut run = Run::new();
1880 run.go("#if 0\n#error not this one\n#else\n#error unsupported target\n#endif\n");
1881 assert_eq!(run.messages(), vec!["unsupported target".to_owned()]);
1882 }
1883
1884 #[test]
1885 fn warning_is_a_warning() {
1886 let mut run = Run::new();
1887 run.go("#warning this is fine\n");
1888 assert_eq!(run.severities(), vec![Severity::Warning]);
1889 assert_eq!(run.messages(), vec!["this is fine".to_owned()]);
1890 }
1891
1892 #[test]
1893 fn an_unterminated_conditional_is_reported() {
1894 let mut run = Run::new();
1895 assert_eq!(run.go("#if 1\nyes\n"), "yes");
1896 assert_eq!(run.messages(), vec!["unterminated `#if`".to_owned()]);
1897 }
1898
1899 #[test]
1900 fn a_conditional_without_an_if_is_reported() {
1901 let mut run = Run::new();
1902 run.go("#endif\n");
1903 assert_eq!(run.messages(), vec!["`#endif` without `#if`".to_owned()]);
1904
1905 let mut run = Run::new();
1906 run.go("#if 1\n#else\n#else\n#endif\n");
1907 assert_eq!(run.messages(), vec!["a second `#else`".to_owned()]);
1908
1909 let mut run = Run::new();
1910 run.go("#if 1\n#else\n#elif 1\n#endif\n");
1911 assert_eq!(run.messages(), vec!["`#elif` after `#else`".to_owned()]);
1912 }
1913
1914 #[test]
1915 fn tokens_after_endif_are_a_warning_rather_than_an_error() {
1916 let mut run = Run::new();
1919 assert_eq!(run.go("#if 1\nyes\n#endif FOO\n"), "yes");
1920 assert_eq!(run.severities(), vec![Severity::Warning]);
1921 assert_eq!(run.messages(), vec!["extra tokens after `#endif`".to_owned()]);
1922 }
1923
1924 #[test]
1925 fn the_null_directive_does_nothing() {
1926 assert_eq!(clean("#\na\n#\nb\n"), "a b");
1927 }
1928
1929 #[test]
1930 fn an_unknown_directive_is_an_error_when_the_region_is_live() {
1931 let mut run = Run::new();
1932 run.go("#frobnicate\n");
1933 assert_eq!(run.messages(), vec!["invalid preprocessing directive".to_owned()]);
1934 }
1935
1936 #[test]
1937 fn line_is_recorded_for_the_source_map() {
1938 let mut run = Run::new();
1939 run.go("#line 42 \"other.c\"\n");
1940 assert!(run.messages().is_empty());
1941 let recorded = run.pp.line_directives();
1942 assert_eq!(recorded.len(), 1);
1943 assert_eq!(recorded[0].line, 42);
1944 let file = recorded[0].file.expect("a file name was given");
1945 assert_eq!(run.interner.resolve(file), "\"other.c\"");
1946 }
1947
1948 #[test]
1949 fn line_moves_what_line_and_file_the_lines_after_it_are_on() {
1950 let mut run = Run::new();
1951 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
1952 assert_eq!(run.go("#line 1000\n__LINE__ __FILE__\n__LINE__\n"), "1000 \"/main.c\" 1001");
1953 }
1954
1955 #[test]
1956 fn a_name_on_the_directive_is_the_name_from_there_on() {
1957 let mut run = Run::new();
1958 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
1959 assert_eq!(run.go("#line 7 \"gen.y\"\n__FILE__ __LINE__\n"), "\"gen.y\" 7");
1960 }
1961
1962 #[test]
1963 fn a_directive_with_no_name_keeps_the_one_already_in_force() {
1964 let mut run = Run::new();
1965 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
1966 assert_eq!(run.go("#line 7 \"gen.y\"\n#line 20\n__FILE__ __LINE__\n"), "\"gen.y\" 20");
1967 }
1968
1969 #[test]
1970 fn the_number_is_expanded_first_because_line_plus_one_is_real_code() {
1971 let mut run = Run::new();
1972 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
1973 assert_eq!(run.go("#define WHERE 300\n#line WHERE\n__LINE__\n"), "300");
1974 }
1975
1976 #[test]
1977 fn a_directive_in_a_header_does_not_move_the_file_that_included_it() {
1978 let mut run = Run::new();
1979 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
1980 run.file("/h.h", "#line 500\n__LINE__\n");
1981 run.dir("/");
1982 assert_eq!(run.go("#include <h.h>\n__LINE__\n"), "500 2");
1983 }
1984
1985 #[test]
1986 fn extra_tokens_after_the_file_name_are_a_warning_and_not_an_error() {
1987 let mut run = Run::new();
1988 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
1989 assert_eq!(run.go("#line 7 \"gen.y\" and more\n__LINE__\n"), "7");
1990 assert_eq!(run.messages(), vec!["extra tokens after `#line`".to_owned()]);
1991 }
1992
1993 #[test]
1994 fn a_line_number_out_of_range_is_refused() {
1995 let mut run = Run::new();
1996 run.go("#line 0\n");
1997 assert_eq!(run.messages(), vec!["`#line` number is out of range".to_owned()]);
1998
1999 let mut run = Run::new();
2000 run.go("#line notanumber\n");
2001 assert_eq!(run.messages(), vec!["`#line` needs a decimal line number".to_owned()]);
2002 }
2003
2004 #[test]
2005 fn a_pragma_passes_through_unchanged() {
2006 assert_eq!(clean("#pragma pack(1)\nint x;\n"), "#pragma pack(1) int x;");
2007 }
2008
2009 #[test]
2014 fn the_space_between_the_hash_and_the_word_comes_off_a_pragma_that_is_indented() {
2015 assert_eq!(clean("#if 1\n# pragma pack(1)\n#endif\n"), "#pragma pack(1)");
2016 assert_eq!(clean("# pragma pack( 1 )\n"), "#pragma pack( 1 )", "the rest is kept");
2017 }
2018
2019 #[test]
2020 fn the_pragma_operator_becomes_a_pragma() {
2021 assert_eq!(
2022 clean("_Pragma(\"GCC visibility push(default)\")\nint x;\n"),
2023 "#pragma GCC visibility push(default) int x;"
2024 );
2025 }
2026
2027 #[test]
2028 fn the_pragma_operator_works_from_inside_a_macro() {
2029 let src = "#define PUSH _Pragma(\"pack(push)\")\nPUSH\nint x;\n";
2032 assert_eq!(clean(src), "#pragma pack(push) int x;");
2033 }
2034
2035 #[test]
2039 fn what_follows_a_pragma_operator_starts_a_line() {
2040 let mut run = Run::new();
2041 let out = run.raw("int x; _Pragma(\"pack(1)\") int y;\n");
2042 let starts: Vec<_> =
2043 out.iter().map(|tok| tok.flags.has(TokenFlags::START_OF_LINE)).collect();
2044 assert_eq!(
2047 starts,
2048 vec![true, false, false, true, false, false, false, false, false, true, false, false]
2049 );
2050 }
2051
2052 #[test]
2053 fn a_pragma_operator_that_is_not_given_a_string_is_reported() {
2054 let mut run = Run::new();
2055 run.go("_Pragma(x)\n");
2056 assert_eq!(run.messages(), vec!["`_Pragma` takes a single string literal".to_owned()]);
2057 }
2058
2059 #[test]
2060 fn an_include_reads_the_file_it_names() {
2061 let mut run = Run::new();
2062 run.file("/dir/one.h", "int from_the_header;\n");
2063 run.dir("/dir");
2064 assert_eq!(run.go("#include <one.h>\nint after;\n"), "int from_the_header; int after;");
2065 assert!(run.messages().is_empty());
2066 }
2067
2068 #[test]
2069 fn a_quoted_include_looks_next_to_the_including_file_first() {
2070 let mut run = Run::new();
2071 run.file("/local.h", "beside\n");
2072 run.file("/dir/local.h", "on the path\n");
2073 run.dir("/dir");
2074 assert_eq!(run.go("#include \"local.h\"\n"), "beside");
2075 assert!(run.messages().is_empty());
2076 }
2077
2078 #[test]
2079 fn an_angled_include_does_not_look_next_to_the_including_file() {
2080 let mut run = Run::new();
2081 run.file("/local.h", "beside\n");
2082 run.file("/dir/local.h", "on the path\n");
2083 run.dir("/dir");
2084 assert_eq!(run.go("#include <local.h>\n"), "on the path");
2085 }
2086
2087 #[test]
2088 fn a_macro_defined_in_a_header_is_visible_after_the_include() {
2089 let mut run = Run::new();
2090 run.file("/dir/defs.h", "#define N 42\n");
2091 run.dir("/dir");
2092 assert_eq!(run.go("#include <defs.h>\nint a = N;\n"), "int a = 42;");
2093 assert!(run.messages().is_empty());
2094 }
2095
2096 #[test]
2097 fn an_include_guard_keeps_the_second_read_empty() {
2098 let mut run = Run::new();
2099 run.file("/dir/g.h", "#ifndef G\n#define G\nonce\n#endif\n");
2100 run.dir("/dir");
2101 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
2102 assert!(run.messages().is_empty());
2103 assert_eq!(run.files(), 2, "the second include is not opened at all");
2104 }
2105
2106 #[test]
2107 fn the_other_spelling_of_a_guard_is_recognised_too() {
2108 for guard in ["#if !defined(G)", "#if !defined G"] {
2109 let mut run = Run::new();
2110 run.file("/dir/g.h", &format!("{guard}\n#define G\nonce\n#endif\n"));
2111 run.dir("/dir");
2112 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
2113 assert_eq!(run.files(), 2, "{guard} should be a guard");
2114 }
2115 }
2116
2117 #[test]
2118 fn a_conditional_that_is_not_a_guard_does_not_skip_anything() {
2119 let mut run = Run::new();
2122 run.file("/dir/g.h", "#ifndef G\ntwice\n#endif\n");
2123 run.dir("/dir");
2124 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "twice twice");
2125 assert_eq!(run.files(), 3);
2126 }
2127
2128 #[test]
2129 fn a_token_outside_the_guard_stops_it_being_a_guard() {
2130 let mut run = Run::new();
2131 run.file("/dir/g.h", "#ifndef G\n#define G\n#endif\nalways\n");
2132 run.dir("/dir");
2133 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "always always");
2134 assert_eq!(run.files(), 3);
2135 }
2136
2137 #[test]
2138 fn pragma_once_skips_the_second_read_and_does_not_reach_the_output() {
2139 let mut run = Run::new();
2140 run.file("/dir/o.h", "#pragma once\nonce\n");
2141 run.dir("/dir");
2142 assert_eq!(run.go("#include <o.h>\n#include <o.h>\n"), "once");
2143 assert!(run.messages().is_empty());
2144 assert_eq!(run.files(), 2);
2145 }
2146
2147 #[test]
2148 fn pragma_once_in_the_main_file_is_a_warning() {
2149 let mut run = Run::new();
2152 assert_eq!(run.go("#pragma once\nx\n"), "x");
2153 assert_eq!(run.severities(), vec![Severity::Warning]);
2154 assert_eq!(run.messages(), vec!["`#pragma once` in the main file".to_owned()]);
2155 }
2156
2157 #[test]
2158 fn any_other_pragma_still_passes_through() {
2159 assert_eq!(clean("#pragma once_upon_a_time\n"), "#pragma once_upon_a_time");
2160 }
2161
2162 #[test]
2165 fn push_macro_and_pop_macro_put_a_definition_aside_and_bring_it_back() {
2166 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";
2167 assert_eq!(clean(src), "a 2 b 1");
2168 }
2169
2170 #[test]
2171 fn a_name_with_no_definition_pushes_and_pops_the_absence() {
2172 let src = "#pragma push_macro(\"X\")\n#define X 1\na X\n#pragma pop_macro(\"X\")\nb X\n";
2175 assert_eq!(clean(src), "a 1 b X");
2176 }
2177
2178 #[test]
2179 fn the_pushes_nest() {
2180 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";
2181 assert_eq!(clean(src), "a 3 b 2 c 1");
2182 }
2183
2184 #[test]
2185 fn a_pop_with_nothing_pushed_says_nothing() {
2186 assert_eq!(clean("#define X 1\n#pragma pop_macro(\"X\")\nX\n"), "1");
2189 assert_eq!(clean("#pragma pop_macro(\"Never\")\nx\n"), "x");
2190 }
2191
2192 #[test]
2193 fn the_pragma_operator_spelling_works_and_takes_effect_where_it_is_written() {
2194 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";
2199 assert_eq!(clean(src), "a 2 b 1");
2200 }
2201
2202 #[test]
2203 fn the_gcc_spelling_is_not_one_of_these_and_passes_through() {
2204 let src = "#define X 1\n#pragma GCC push_macro(\"X\")\n#undef X\n#define X 2\nX\n";
2208 assert_eq!(clean(src), "#pragma GCC push_macro(\"X\") 2");
2209 }
2210
2211 #[test]
2212 fn a_push_macro_that_is_not_the_shape_is_an_error() {
2213 for src in ["#pragma push_macro\n", "#pragma push_macro(X)\n", "#pragma pop_macro()\n"] {
2214 let mut run = Run::new();
2215 run.go(src);
2216 let word = if src.contains("push") { "push" } else { "pop" };
2217 assert_eq!(
2218 run.messages(),
2219 vec![format!("invalid `#pragma {word}_macro` directive")],
2220 "from {src:?}"
2221 );
2222 }
2223 }
2224
2225 #[test]
2226 fn a_string_that_does_not_spell_one_identifier_names_no_macro() {
2227 assert_eq!(clean("#pragma push_macro(\"a b\")\nx\n"), "x");
2230 assert_eq!(clean("#pragma push_macro(\"2\")\nx\n"), "x");
2231 }
2232
2233 #[test]
2234 fn what_follows_the_closing_parenthesis_is_the_usual_warning() {
2235 let mut run = Run::new();
2236 assert_eq!(run.go("#define X 1\n#pragma push_macro(\"X\") junk\nX\n"), "1");
2237 assert_eq!(run.severities(), vec![Severity::Warning]);
2238 assert_eq!(run.messages(), vec!["extra tokens after `#pragma`".to_owned()]);
2239 }
2240
2241 #[test]
2242 fn has_include_answers_from_the_search_path() {
2243 let mut run = Run::new();
2244 run.file("/dir/there.h", "");
2245 run.dir("/dir");
2246 let src = "#if __has_include(<there.h>)\nyes\n#endif\n\
2247 #if __has_include(<gone.h>)\nno\n#endif\n";
2248 assert_eq!(run.go(src), "yes");
2249 assert!(run.messages().is_empty(), "a header that is not there is an answer, not an error");
2250 }
2251
2252 #[test]
2253 fn has_include_asks_the_question_the_include_on_the_same_line_would() {
2254 let mut run = Run::new();
2258 run.file("/beside.h", "");
2259 let src = "#if __has_include(\"beside.h\")\nquoted\n#endif\n\
2260 #if __has_include(<beside.h>)\nangled\n#endif\n";
2261 assert_eq!(run.go(src), "quoted");
2262 }
2263
2264 #[test]
2265 fn has_include_next_starts_where_include_next_would() {
2266 let mut run = Run::new();
2267 run.file("/a/both.h", "#if __has_include_next(<both.h>)\nmore\n#endif\n");
2268 run.file("/b/both.h", "last\n");
2269 run.file("/a/only.h", "#if __has_include_next(<only.h>)\nmore\n#endif\n");
2270 run.dir("/a");
2271 run.dir("/b");
2272 assert_eq!(run.go("#include <both.h>\n"), "more");
2273 assert_eq!(run.go("#include <only.h>\n"), "", "there is nothing after /a to find it in");
2274 }
2275
2276 #[test]
2277 fn the_operand_of_has_include_is_not_macro_expanded() {
2278 let mut run = Run::new();
2281 run.file("/dir/linux/version.h", "");
2282 run.dir("/dir");
2283 let src = "#define linux 1\n#if __has_include(<linux/version.h>)\nyes\n#endif\n";
2284 assert_eq!(run.go(src), "yes");
2285 }
2286
2287 #[test]
2288 fn a_macro_may_expand_to_a_has_include() {
2289 let mut run = Run::new();
2291 run.file("/dir/there.h", "");
2292 run.dir("/dir");
2293 let src = "#define HAVE __has_include(<there.h>)\n#if HAVE\nyes\n#endif\n";
2294 assert_eq!(run.go(src), "yes");
2295 }
2296
2297 #[test]
2298 fn defined_says_the_has_operators_are_there() {
2299 let src = "#if defined(__has_include) && defined __has_builtin\nyes\n#endif\n";
2302 assert_eq!(clean(src), "yes");
2303 assert_eq!(clean("#ifdef __has_attribute\nyes\n#endif\n"), "yes");
2304 }
2305
2306 #[test]
2307 fn has_attribute_answers_out_of_the_matrix() {
2308 assert_eq!(clean("#if __has_attribute(packed)\nyes\n#endif\n"), "");
2311 assert_eq!(clean("#if __has_attribute(no_such_attribute)\nyes\n#endif\n"), "");
2312 assert_eq!(clean("#if !__has_attribute(packed)\nno\n#endif\n"), "no");
2313 }
2314
2315 #[test]
2316 fn the_scoped_spelling_of_an_attribute_is_the_same_question() {
2317 assert_eq!(clean("#if __has_c_attribute(gnu::packed)\nyes\n#endif\n"), "");
2321 assert_eq!(clean("#if __has_c_attribute(deprecated)\nyes\n#endif\n"), "");
2322 }
2323
2324 #[test]
2325 fn has_builtin_answers_no_until_the_builtin_is_real() {
2326 assert_eq!(clean("#if __has_builtin(__builtin_expect)\nyes\n#endif\n"), "");
2327 assert_eq!(clean("#if __has_builtin(__builtin_nonesuch)\nyes\n#endif\n"), "");
2328 }
2329
2330 #[test]
2331 fn has_feature_and_has_extension_read_the_same_table() {
2332 assert_eq!(clean("#if __has_feature(pragma_once)\nyes\n#endif\n"), "yes");
2335 assert_eq!(clean("#if __has_extension(pragma_once)\nyes\n#endif\n"), "yes");
2336 assert_eq!(clean("#if __has_extension(include_next)\nyes\n#endif\n"), "yes");
2337 assert_eq!(clean("#if __has_feature(include_next)\nyes\n#endif\n"), "");
2338 assert_eq!(clean("#if __has_feature(statement_expressions)\nyes\n#endif\n"), "");
2339 }
2340
2341 #[test]
2342 fn building_module_is_always_no_and_is_recognised_so_that_the_line_parses() {
2343 assert_eq!(clean("#if __building_module(m)\nyes\n#endif\n"), "");
2347 assert_eq!(clean("#if !__building_module(m)\nyes\n#endif\n"), "yes");
2348 assert_eq!(
2349 clean(
2350 "#if !defined(offsetof) || (__has_feature(modules) && !__building_module(x))\nyes\n#endif\n"
2351 ),
2352 "yes"
2353 );
2354 assert_eq!(clean("#ifdef __building_module\nyes\n#endif\n"), "yes");
2356 assert_eq!(clean("#if defined(__building_module)\nyes\n#endif\n"), "yes");
2357 }
2358
2359 #[test]
2360 fn a_has_operator_without_an_operand_is_reported() {
2361 let mut run = Run::new();
2362 run.go("#if __has_include\nyes\n#endif\n");
2363 assert_eq!(run.messages(), ["expected `(` after `__has_include`"]);
2364 let mut run = Run::new();
2365 run.go("#if __has_include(1)\nyes\n#endif\n");
2366 assert_eq!(run.messages(), ["expected a file name in `<>` or `\"\"`"]);
2367 let mut run = Run::new();
2368 run.go("#if __has_attribute(\"packed\")\nyes\n#endif\n");
2369 assert_eq!(run.messages(), ["expected an identifier as the operand of `__has_attribute`"]);
2370 }
2371
2372 #[test]
2373 fn the_has_operators_answer_in_ordinary_text_too() {
2374 assert_eq!(clean("f __has_feature(pragma_once)\n"), "f 1");
2378 assert_eq!(clean("b __has_builtin(__builtin_expect)\n"), "b 0");
2379 assert_eq!(clean("a __has_attribute(packed)\n"), "a 0");
2380 assert_eq!(clean("c __has_c_attribute(deprecated)\n"), "c 0");
2381 assert_eq!(clean("m __building_module(foo)\n"), "m 0");
2382 }
2383
2384 #[test]
2385 fn a_macro_that_expands_to_a_has_operator_is_answered_where_it_is_used() {
2386 assert_eq!(clean("#define HAVE __has_feature(pragma_once)\nx HAVE\n"), "x 1");
2389 assert_eq!(clean("#define HAVE(x) __has_attribute(x)\ny HAVE(packed)\n"), "y 0");
2390 }
2391
2392 #[test]
2393 fn a_has_operator_in_text_still_needs_its_operand() {
2394 let mut run = Run::new();
2395 run.go("tail __has_attribute;\n");
2396 assert_eq!(run.messages(), ["expected `(` after `__has_attribute`"]);
2397 }
2398
2399 #[test]
2400 fn the_header_operators_are_refused_in_ordinary_text() {
2401 let mut run = Run::new();
2404 run.file("/dir/there.h", "");
2405 run.dir("/dir");
2406 run.go("a __has_include(<there.h>)\n");
2407 assert_eq!(run.messages(), ["`__has_include` used outside of a preprocessing directive"]);
2408 let mut run = Run::new();
2409 run.go("b __has_include_next(\"x.h\")\n");
2410 assert_eq!(
2411 run.messages(),
2412 ["`__has_include_next` used outside of a preprocessing directive"]
2413 );
2414 }
2415
2416 #[test]
2417 fn the_predefined_set_is_visible_to_the_source_file() {
2418 let mut run = Run::new();
2419 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2420 let src = "#if defined(__x86_64__) && defined(__linux__) && __SIZEOF_LONG__ == 8\n\
2421 yes\n#endif\n";
2422 assert_eq!(run.go(src), "yes");
2423 assert!(run.messages().is_empty());
2424 }
2425
2426 #[test]
2427 fn the_predefined_set_follows_the_target_and_not_the_host() {
2428 let mut run = Run::new();
2429 run.predefine("aarch64-unknown-linux-gnu", &Predef::new());
2430 assert_eq!(
2431 run.go("#ifdef __x86_64__\nno\n#endif\n#ifdef __aarch64__\nyes\n#endif\n"),
2432 "yes"
2433 );
2434 }
2435
2436 #[test]
2437 fn a_predefined_macro_expands_where_it_is_used() {
2438 let mut run = Run::new();
2439 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2440 assert_eq!(run.go("__SIZE_TYPE__ n;\n"), "long unsigned int n;");
2441 }
2442
2443 #[test]
2444 fn a_command_line_define_is_a_definition_like_any_other() {
2445 let mut opts = Predef::new();
2446 opts.defines = vec!["FOO".to_owned(), "BAR=3".to_owned()];
2447 opts.undefines = vec!["__linux__".to_owned()];
2448 let mut run = Run::new();
2449 run.predefine("x86_64-unknown-linux-gnu", &opts);
2450 let src = "#if FOO && BAR == 3 && !defined(__linux__)\nyes\n#endif\n";
2451 assert_eq!(run.go(src), "yes");
2452 assert!(run.messages().is_empty());
2453 }
2454
2455 #[test]
2456 fn the_predefined_set_produces_no_tokens_of_its_own() {
2457 let mut run = Run::new();
2460 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2461 assert_eq!(run.go("alone\n"), "alone");
2462 }
2463
2464 #[test]
2465 fn the_predefined_files_are_named_the_way_gcc_names_them() {
2466 let mut run = Run::new();
2467 let mut opts = Predef::new();
2468 opts.defines = vec!["FOO=1".to_owned()];
2469 run.predefine("x86_64-unknown-linux-gnu", &opts);
2470 let names: Vec<&str> = run.sources.files().iter().map(|f| f.name.as_str()).collect();
2471 assert_eq!(names, ["<built-in>", "<command-line>"]);
2472 }
2473
2474 #[test]
2475 fn a_dialect_without_the_gnu_extensions_says_so() {
2476 let mut opts = Predef::new();
2477 opts.gnu_extensions = false;
2478 opts.std = Std::C99;
2479 let mut run = Run::new();
2480 run.predefine("x86_64-unknown-linux-gnu", &opts);
2481 let src = "#if defined(__STRICT_ANSI__) && __STDC_VERSION__ == 199901L && !defined(linux)\n\
2482 yes\n#endif\n";
2483 assert_eq!(run.go(src), "yes");
2484 }
2485
2486 #[test]
2487 fn the_date_and_time_are_the_same_for_the_whole_translation_unit() {
2488 let mut opts = Predef::new();
2489 opts.timestamp = Timestamp::from_unix(0);
2490 let mut run = Run::new();
2491 run.predefine("x86_64-unknown-linux-gnu", &opts);
2492 assert_eq!(run.go("__DATE__ __TIME__\n"), "\"Jan 1 1970\" \"00:00:00\"");
2493 }
2494
2495 #[test]
2496 fn a_has_operator_in_a_dead_branch_is_not_asked_about() {
2497 assert_eq!(clean("#if 0\n#if __has_include\n#endif\n#endif\nafter\n"), "after");
2499 }
2500
2501 #[test]
2502 fn a_conditional_may_not_span_an_include() {
2503 let mut run = Run::new();
2507 run.file("/dir/open.h", "#if 1\n");
2508 run.dir("/dir");
2509 run.go("#include <open.h>\nkept\n#endif\n");
2510 let messages = run.messages();
2511 assert_eq!(messages.len(), 2);
2512 assert!(messages[0].contains("unterminated"));
2513 assert!(messages[1].contains("without"));
2514 }
2515
2516 #[test]
2517 fn include_next_continues_after_the_directory_the_file_came_from() {
2518 let mut run = Run::new();
2521 run.file("/a/limits.h", "wrapper\n#include_next <limits.h>\n");
2522 run.file("/b/limits.h", "real\n");
2523 run.dir("/a");
2524 run.dir("/b");
2525 assert_eq!(run.go("#include <limits.h>\n"), "wrapper real");
2526 assert!(run.messages().is_empty());
2527 }
2528
2529 #[test]
2530 fn a_computed_include_is_expanded_first() {
2531 let mut run = Run::new();
2532 run.file("/dir/sub/thing.h", "computed\n");
2533 run.dir("/dir");
2534 let src = "#define HEADER <sub/thing.h>\n#include HEADER\n";
2535 assert_eq!(run.go(src), "computed");
2536 assert!(run.messages().is_empty());
2537 let mut run = Run::new();
2539 run.file("/dir/sub/thing.h", "computed\n");
2540 run.dir("/dir");
2541 assert_eq!(run.go("#define H \"sub/thing.h\"\n#include H\n"), "computed");
2542 }
2543
2544 #[test]
2545 fn a_header_that_is_not_there_says_where_it_looked() {
2546 let mut run = Run::new();
2547 run.dir("/dir");
2548 run.go("#include <nope.h>\n");
2549 let diagnostics = run.pp.take_diagnostics();
2550 assert_eq!(diagnostics.len(), 1);
2551 assert_eq!(diagnostics[0].code, Some("E0341"));
2552 assert_eq!(diagnostics[0].message, "`nope.h` file not found");
2553 assert!(diagnostics[0].children[0].message.contains("/dir"));
2554 }
2555
2556 #[test]
2557 fn an_include_that_is_not_a_header_name_is_reported() {
2558 let mut run = Run::new();
2559 run.go("#include 3\n");
2560 let diagnostics = run.pp.take_diagnostics();
2561 assert_eq!(diagnostics[0].code, Some("E0343"));
2562 }
2563
2564 #[test]
2565 fn a_header_that_includes_itself_stops() {
2566 let mut run = Run::new();
2567 run.file("/dir/loop.h", "#include <loop.h>\n");
2568 run.dir("/dir");
2569 run.go("#include <loop.h>\n");
2570 let diagnostics = run.pp.take_diagnostics();
2571 assert_eq!(diagnostics.len(), 1, "one complaint, not one per level");
2572 assert_eq!(diagnostics[0].code, Some("E0342"));
2573 }
2574
2575 #[test]
2576 fn an_include_in_a_dead_branch_is_not_read() {
2577 let mut run = Run::new();
2578 assert_eq!(run.go("#if 0\n#include <nothing.h>\n#endif\nafter\n"), "after");
2579 assert!(run.messages().is_empty(), "a skipped include is not resolved");
2580 }
2581
2582 #[test]
2583 fn embed_writes_the_bytes_of_the_resource() {
2584 let mut run = Run::new();
2585 run.bytes("/logo.bin", &[0, 1, 127, 128, 255]);
2586 assert_eq!(run.go("#embed \"logo.bin\"\n"), "0, 1, 127, 128, 255");
2587 assert!(run.messages().is_empty());
2588 }
2589
2590 #[test]
2591 fn an_embed_is_a_valid_initializer_on_both_sides_of_empty() {
2592 let mut run = Run::new();
2597 run.bytes("/some.bin", &[7, 8]);
2598 run.bytes("/none.bin", &[]);
2599 let line = |name: &str| {
2600 format!("{{\n#embed \"{name}\" prefix(0xEF,) suffix(,0xFE) if_empty(0)\n}}\n")
2601 };
2602 assert_eq!(run.go(&line("some.bin")), "{ 0xEF,7, 8 ,0xFE }");
2603 assert_eq!(run.go_named("/other.c", &line("none.bin")), "{ 0 }");
2604 assert!(run.messages().is_empty());
2605 }
2606
2607 #[test]
2608 fn the_limit_and_the_offset_choose_a_window_of_the_resource() {
2609 let mut run = Run::new();
2610 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
2611 assert_eq!(run.go("#embed \"eight.bin\" limit(3)\n"), "1, 2, 3");
2612 assert_eq!(
2613 run.go_named("/b.c", "#embed \"eight.bin\" gnu::offset(4) limit(3)\n"),
2614 "5, 6, 7"
2615 );
2616 assert_eq!(run.go_named("/c.c", "#embed \"eight.bin\" limit(0) if_empty(9)\n"), "9");
2619 assert_eq!(run.go_named("/d.c", "#embed \"eight.bin\" gnu::offset(99)\n"), "");
2620 assert!(run.messages().is_empty());
2621 }
2622
2623 #[test]
2624 fn the_limit_is_a_constant_expression_and_not_just_a_number() {
2625 let mut run = Run::new();
2628 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
2629 assert_eq!(
2630 run.go("#define CHUNK 2\n#embed \"eight.bin\" limit(CHUNK * 2)\n"),
2631 "1, 2, 3, 4"
2632 );
2633 assert!(run.messages().is_empty());
2634 }
2635
2636 #[test]
2637 fn a_misspelled_embed_parameter_is_refused_rather_than_ignored() {
2638 let mut run = Run::new();
2641 run.bytes("/eight.bin", &[1, 2]);
2642 assert_eq!(run.go("#embed \"eight.bin\" limits(1)\n"), "");
2643 assert_eq!(run.messages(), vec!["unknown `#embed` parameter `limits`".to_owned()]);
2644 let mut vendor = Run::new();
2645 vendor.bytes("/eight.bin", &[1, 2]);
2646 assert_eq!(vendor.go("#embed \"eight.bin\" clang::offset(1)\n"), "");
2647 assert_eq!(
2648 vendor.messages(),
2649 vec!["unknown `#embed` parameter `clang::offset`".to_owned()]
2650 );
2651 }
2652
2653 #[test]
2654 fn a_missing_embed_resource_is_reported_as_a_resource() {
2655 let mut run = Run::new();
2656 run.go("#embed <nothing.bin>\n");
2657 assert_eq!(run.messages(), vec!["`nothing.bin` resource not found".to_owned()]);
2658 }
2659
2660 #[test]
2661 fn has_embed_tells_missing_from_present_from_empty() {
2662 let mut run = Run::new();
2666 run.bytes("/some.bin", &[1]);
2667 run.bytes("/none.bin", &[]);
2668 let src = "#if __has_embed(\"none.bin\") == __STDC_EMBED_EMPTY__\nempty\n#endif\n\
2669 #if __has_embed(\"some.bin\") == __STDC_EMBED_FOUND__\nfound\n#endif\n\
2670 #if __has_embed(\"gone.bin\") == __STDC_EMBED_NOT_FOUND__\ngone\n#endif\n";
2671 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
2672 assert_eq!(run.go(src), "empty found gone");
2673 assert!(run.messages().is_empty());
2674 }
2675
2676 #[test]
2677 fn has_embed_takes_the_limit_into_account() {
2678 let mut run = Run::new();
2681 run.bytes("/some.bin", &[1, 2, 3]);
2682 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
2683 let src = "#if __has_embed(\"some.bin\" limit(0)) == __STDC_EMBED_EMPTY__\nempty\n#endif\n";
2684 assert_eq!(run.go(src), "empty");
2685 assert!(run.messages().is_empty());
2686 }
2687
2688 #[test]
2689 fn a_directive_may_have_space_before_the_hash_and_after_it() {
2690 assert_eq!(clean(" # define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
2691 }
2692
2693 #[test]
2694 fn a_definition_survives_across_a_conditional() {
2695 assert_eq!(clean("#if 1\n#define F 7\n#endif\nF\n"), "7");
2696 }
2697
2698 #[test]
2699 fn an_empty_if_expression_is_reported() {
2700 let mut run = Run::new();
2701 run.go("#if\n#endif\n");
2702 assert_eq!(run.messages(), vec!["`#if` with no expression".to_owned()]);
2703 }
2704
2705 #[test]
2706 fn the_file_and_the_line_say_where_the_use_is() {
2707 let mut run = Run::new();
2708 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2709 assert_eq!(run.go("__FILE__ __LINE__\n__LINE__\n"), "\"/main.c\" 1 2");
2710 assert!(run.messages().is_empty());
2711 }
2712
2713 #[test]
2714 fn a_macro_that_mentions_the_line_answers_with_the_call() {
2715 let mut run = Run::new();
2716 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2717 run.file("/where.h", "#define WHERE __FILE__ __LINE__\n");
2718 assert_eq!(run.go("#include \"where.h\"\n\n\nWHERE\n"), "\"/main.c\" 4");
2722 assert!(run.messages().is_empty());
2723 }
2724
2725 #[test]
2726 fn the_file_name_is_the_file_without_the_directories() {
2727 let mut run = Run::new();
2728 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2729 assert_eq!(run.go_named("/deep/down/main.c", "__FILE_NAME__\n"), "\"main.c\"");
2730 }
2731
2732 #[test]
2733 fn a_backslash_in_the_name_is_escaped() {
2734 let mut run = Run::new();
2735 run.predefine("x86_64-pc-windows-msvc", &Predef::new());
2736 let text = run.go_named("C:\\src\\main.c", "__FILE__ __FILE_NAME__\n");
2739 assert_eq!(text, "\"C:\\\\src\\\\main.c\" \"main.c\"");
2740 }
2741
2742 #[test]
2743 fn the_base_file_is_the_one_named_on_the_command_line() {
2744 let mut run = Run::new();
2745 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2746 run.file("/deep.h", "__FILE__ __BASE_FILE__\n");
2747 assert_eq!(run.go("#include \"deep.h\"\n"), "\"/deep.h\" \"/main.c\"");
2748 assert!(run.messages().is_empty());
2749 }
2750
2751 #[test]
2752 fn the_include_level_counts_the_headers_above_it() {
2753 let mut run = Run::new();
2754 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2755 run.file("/one.h", "__INCLUDE_LEVEL__\n#include \"two.h\"\n");
2756 run.file("/two.h", "__INCLUDE_LEVEL__\n");
2757 assert_eq!(run.go("__INCLUDE_LEVEL__\n#include \"one.h\"\n"), "0 1 2");
2758 assert!(run.messages().is_empty());
2759 }
2760
2761 #[test]
2762 fn the_counter_is_a_different_number_every_time() {
2763 let mut run = Run::new();
2764 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2765 assert_eq!(run.go("__COUNTER__ __COUNTER__ __COUNTER__\n"), "0 1 2");
2766 }
2767
2768 #[test]
2769 fn the_counter_advances_once_per_argument_rather_than_once_per_use() {
2770 let mut run = Run::new();
2771 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2772 assert_eq!(run.go("#define TWICE(x) x x\nTWICE(__COUNTER__) __COUNTER__\n"), "0 0 1");
2776 }
2777
2778 #[test]
2779 fn the_line_is_a_number_an_if_can_use() {
2780 let mut run = Run::new();
2781 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2782 assert_eq!(run.go("#if __LINE__ == 1 && __INCLUDE_LEVEL__ == 0\nyes\n#endif\n"), "yes");
2783 assert!(run.messages().is_empty());
2784 }
2785
2786 #[test]
2787 fn the_dynamic_macros_are_defined_like_any_others() {
2788 let mut run = Run::new();
2789 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2790 let src = "#ifdef __FILE__\nyes\n#endif\n#undef __LINE__\n#ifndef __LINE__\ngone\n#endif\n";
2791 assert_eq!(run.go(src), "yes gone");
2792 assert!(run.messages().is_empty(), "`#undef` of a builtin is allowed, as it is in GCC");
2793 }
2794
2795 #[test]
2796 fn redefining_a_dynamic_macro_warns_and_points_at_the_built_in_file() {
2797 let mut run = Run::new();
2798 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2799 assert_eq!(run.go("#define __FILE__ \"mine.c\"\n__FILE__\n"), "\"mine.c\"");
2800 let complaints = run.pp.take_diagnostics();
2801 assert_eq!(complaints.len(), 1);
2802 assert_eq!(complaints[0].code, Some("W0301"));
2803 let previous = complaints[0].children.first().expect("a note saying where it was");
2804 assert_eq!(run.sources.lookup(previous.span.lo).map(|loc| loc.file), {
2805 let built_in = run.sources.files().iter().find(|f| f.name == BUILT_IN);
2806 built_in.map(|f| f.id)
2807 });
2808 }
2809
2810 #[test]
2811 fn destringizing_undoes_what_stringizing_did() {
2812 assert_eq!(destringize(r#""a \"b\" c""#), r#"a "b" c"#);
2813 assert_eq!(destringize(r#""a \\ b""#), r"a \ b");
2814 assert_eq!(destringize(r#"L"wide""#), "wide");
2815 }
2816}