1use std::collections::{HashMap, HashSet};
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, PrefixMap, Preinclude};
25use rucc_target::TargetInfo;
26
27use crate::cond;
28use crate::embed;
29use crate::expand::{Condition, Expander};
30use crate::include::{
31 Context, Dependency, Frame, Header, Reader, directory_of, header_from_token,
32 header_from_tokens, spelling,
33};
34use crate::macros::{Builtin, MacroTable, parse_define};
35use crate::predef::{BUILT_IN, COMMAND_LINE, Predef, built_in, command_line};
36use crate::token::Tok;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40enum Guard {
41 Once,
43 Macro(Symbol),
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52enum Scan {
53 Start,
55 Inside(Symbol),
57 Closed(Symbol),
59 No,
61}
62
63#[derive(Debug)]
65struct Cond {
66 span: Span,
68 live: bool,
71 taken: bool,
74 enclosing_live: bool,
76 seen_else: bool,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct LineDirective {
83 pub span: Span,
85 pub line: u32,
87 pub file: Option<Symbol>,
89 pub at: usize,
98}
99
100#[derive(Debug, Default)]
105pub struct Preprocessor {
106 macros: MacroTable,
107 expander: Expander,
108 diagnostics: Vec<Diagnostic>,
109 conds: Vec<Cond>,
110 lines: Vec<LineDirective>,
111 stack: Vec<Frame>,
113 markers: Vec<String>,
117 seen: HashMap<PathBuf, Guard>,
121 deps: Vec<Dependency>,
128 dep_ids: HashSet<PathBuf>,
140}
141
142impl Preprocessor {
143 pub fn new() -> Preprocessor {
145 Preprocessor::default()
146 }
147
148 pub fn with_prefix_map(map: PrefixMap) -> Preprocessor {
155 Preprocessor { expander: Expander::with_prefix_map(map), ..Preprocessor::default() }
156 }
157
158 pub fn macros(&self) -> &MacroTable {
160 &self.macros
161 }
162
163 pub fn macros_mut(&mut self) -> &mut MacroTable {
165 &mut self.macros
166 }
167
168 pub fn diagnostics(&self) -> &[Diagnostic] {
170 &self.diagnostics
171 }
172
173 pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
175 std::mem::take(&mut self.diagnostics)
176 }
177
178 pub fn dependencies(&self) -> &[Dependency] {
183 &self.deps
184 }
185
186 pub fn line_directives(&self) -> &[LineDirective] {
192 &self.lines
193 }
194
195 pub fn predefine(
207 &mut self,
208 target: &TargetInfo,
209 opts: &Predef,
210 cx: &mut Context<'_>,
211 ) -> Result<(), SourceMapFull> {
212 let names = Names::new(cx.interner);
213 let file = self.synthetic(BUILT_IN, built_in(target, opts), cx, &names)?;
214 let start = cx.sources.file(file).start;
220 for (spelling, builtin) in Builtin::ALL {
221 let name = cx.interner.intern(spelling);
222 self.macros.define_builtin(name, builtin, Span::new(start, start));
223 }
224 let text = command_line(opts);
225 if !text.is_empty() {
226 self.synthetic(COMMAND_LINE, text, cx, &names)?;
227 }
228 Ok(())
229 }
230
231 fn synthetic(
233 &mut self,
234 name: &str,
235 text: String,
236 cx: &mut Context<'_>,
237 names: &Names,
238 ) -> Result<FileId, SourceMapFull> {
239 let file = cx.sources.add(name, text.into_bytes())?;
240 let mut out = Vec::new();
241 let path = PathBuf::from(name);
245 let id = cx.fs.identity(&path);
246 self.stack.push(Frame { at: Span::DUMMY, path, id, dir: None, next: 0 });
247 self.process(file, &mut out, cx, names);
248 self.stack.clear();
249 debug_assert!(out.is_empty(), "{name} is directives only and produces no tokens");
250 Ok(file)
251 }
252
253 pub fn preinclude(
275 &mut self,
276 files: &[Preinclude],
277 out: &mut Vec<Tok>,
278 cx: &mut Context<'_>,
279 ) -> Result<(), SourceMapFull> {
280 if files.is_empty() {
281 return Ok(());
282 }
283 let names = Names::new(cx.interner);
284 let mut text = String::new();
288 let mut order: Vec<(usize, &Preinclude)> = Vec::new();
289 for macros_only in [true, false] {
290 for file in files.iter().filter(|f| f.macros_only == macros_only) {
291 text.push_str(if macros_only { "-imacros " } else { "-include " });
292 order.push((text.len(), file));
293 text.push_str(&file.name);
294 text.push('\n');
295 }
296 }
297 let record = cx.sources.add(COMMAND_LINE, text.into_bytes())?;
298 let start = cx.sources.file(record).start;
299 let path = PathBuf::from(COMMAND_LINE);
303 let id = cx.fs.identity(&path);
304 self.stack.push(Frame { at: Span::DUMMY, path, id, dir: None, next: 0 });
305 let here = Path::new(".");
306 for (offset, file) in order {
307 let at = Span::new(start + offset as u32, start + (offset + file.name.len()) as u32);
308 let form = IncludeForm::Quoted;
309 let found = cx.search.resolve(cx.fs, &file.name, form, Some(here), 0);
310 let Some(found) = found else {
311 let tried = cx.search.tried(&file.name, form, Some(here), 0);
312 self.not_found(&file.name, at, &tried, cx.search.missing_system());
313 continue;
314 };
315 let mut discarded = Vec::new();
316 let sink = if file.macros_only { &mut discarded } else { &mut *out };
317 self.read(found, at, sink, cx, &names);
318 }
319 self.stack.clear();
320 Ok(())
321 }
322
323 pub fn run(&mut self, file: FileId, cx: &mut Context<'_>) -> Vec<Tok> {
328 let names = Names::new(cx.interner);
329 let mut out = Vec::new();
330 let name = cx.sources.file(file).name.clone();
331 let dir = directory_of(&name);
332 let path = PathBuf::from(name);
335 let id = cx.fs.identity(&path);
336 self.stack.push(Frame { at: Span::DUMMY, path, id, dir, next: 0 });
337 self.process(file, &mut out, cx, &names);
338 self.stack.clear();
339 out
340 }
341
342 fn process(&mut self, file: FileId, out: &mut Vec<Tok>, cx: &mut Context<'_>, names: &Names) {
344 let bytes = cx.sources.file(file).shared_bytes();
347 let start = cx.sources.file(file).start;
348 let mut reader = Reader::new(bytes.as_slice(), start, cx.lex);
349 let depth_on_entry = self.conds.len();
350 let mut text: Vec<Tok> = Vec::new();
354 let mut body: Vec<PpToken> = Vec::new();
355 let mut scan = Scan::Start;
356
357 loop {
358 let was_live = self.live();
359 let first = reader.next(cx.interner);
360 if first.is_eof() {
361 break;
362 }
363 if is_directive(first) {
364 self.flush(&mut text, out, cx, names);
365 body.clear();
366 let name_tok = reader.next(cx.interner);
367 if name_tok.is_eof() || name_tok.flags.has(TokenFlags::START_OF_LINE) {
370 reader.put_back(name_tok);
371 continue;
372 }
373 body.push(name_tok);
374 if was_live && is_include(ident_of(&name_tok), names) {
379 if let Some(header) = reader.header_name(cx.interner) {
380 body.push(header);
381 }
382 }
383 reader.line(cx.interner, &mut body);
384 let opens =
385 matches!(scan, Scan::Start).then(|| guard_opener(&body, names)).flatten();
386 self.directive(&body, first.span, out, cx, names);
387 scan = match scan {
388 Scan::Start => match opens {
392 Some(name) if self.conds.len() == depth_on_entry + 1 => Scan::Inside(name),
393 _ => Scan::No,
394 },
395 Scan::Inside(name) if self.conds.len() == depth_on_entry => Scan::Closed(name),
396 Scan::Inside(name) => Scan::Inside(name),
397 Scan::Closed(_) | Scan::No => Scan::No,
398 };
399 } else {
400 body.clear();
401 reader.line(cx.interner, &mut body);
402 if self.live() {
403 let operator = ident_of(&first) == Some(names.pragma_op)
409 || body.iter().any(|t| ident_of(t) == Some(names.pragma_op));
410 if operator {
411 self.flush(&mut text, out, cx, names);
412 }
413 text.push(Tok::new(first));
414 text.extend(body.iter().copied().map(Tok::new));
415 if operator {
416 self.flush(&mut text, out, cx, names);
417 }
418 }
419 if !matches!(scan, Scan::Inside(_)) {
421 scan = Scan::No;
422 }
423 }
424 let complaints = reader.take_diagnostics();
427 if was_live || self.live() {
428 self.diagnostics.extend(complaints);
429 }
430 }
431 self.flush(&mut text, out, cx, names);
432 self.diagnostics.extend(reader.take_diagnostics());
433
434 if let Scan::Closed(name) = scan {
437 if self.macros.is_defined(name) {
438 if let Some(frame) = self.stack.last() {
439 self.seen.entry(frame.id.clone()).or_insert(Guard::Macro(name));
440 }
441 }
442 }
443
444 for cond in self.conds.drain(depth_on_entry..) {
447 self.diagnostics
448 .push(Diagnostic::error("unterminated `#if`", cond.span).with_code("E0330"));
449 }
450 }
451
452 fn live(&self) -> bool {
454 self.conds.last().is_none_or(|c| c.live)
455 }
456
457 fn flush(
459 &mut self,
460 text: &mut Vec<Tok>,
461 out: &mut Vec<Tok>,
462 cx: &mut Context<'_>,
463 names: &Names,
464 ) {
465 if text.is_empty() {
466 return;
467 }
468 let taken = std::mem::take(text);
469 let expanded = self.expander.expand_toks(taken, &self.macros, cx.interner, cx.sources);
470 self.diagnostics.append(&mut self.expander.take_diagnostics());
471 let expanded = self.resolve_has(expanded, cx, names, Pass::Text);
476 self.pragma_operator(expanded, out, cx.interner, names);
477 }
478
479 fn directive(
481 &mut self,
482 body: &[PpToken],
483 hash: Span,
484 out: &mut Vec<Tok>,
485 cx: &mut Context<'_>,
486 names: &Names,
487 ) {
488 let Some(first) = body.first().copied() else {
489 return;
490 };
491 let name = ident_of(&first);
492 let rest = &body[1..];
493
494 if name == Some(names.r#if) {
497 let value = self.live() && self.eval(rest, hash, cx, names);
498 self.open(hash, value);
499 return;
500 }
501 if name == Some(names.ifdef) || name == Some(names.ifndef) {
502 let want = name == Some(names.ifdef);
503 let value = self.live() && self.defined_check(rest, hash, want, names);
504 self.open(hash, value);
505 return;
506 }
507 if name == Some(names.elif) || name == Some(names.elifdef) || name == Some(names.elifndef) {
508 self.elif(name, rest, hash, cx, names);
509 return;
510 }
511 if name == Some(names.r#else) {
512 self.branch_else(rest, hash);
513 return;
514 }
515 if name == Some(names.endif) {
516 self.endif(rest, hash);
517 return;
518 }
519 if !self.live() {
520 return;
524 }
525
526 if name.is_none() && decimal(&first, cx.interner).is_some() {
530 self.line_marker(body, hash, out.len(), cx);
531 return;
532 }
533
534 let interner = &mut *cx.interner;
535 if name == Some(names.define) {
536 let (def, diagnostics) = parse_define(rest, interner);
537 self.diagnostics.extend(diagnostics);
538 if let Some(def) = def {
539 if let Some(problem) = self.macros.define(def, interner) {
540 self.diagnostics.push(problem);
541 }
542 }
543 } else if name == Some(names.undef) {
544 self.undef(rest, hash, interner);
545 } else if name == Some(names.error) || name == Some(names.warning) {
546 self.message(rest, hash, name == Some(names.error), interner);
547 } else if name == Some(names.line) {
548 self.line(rest, hash, out.len(), cx);
549 } else if name == Some(names.pragma) {
550 if rest.len() == 1 && ident_of(&rest[0]) == Some(names.once) {
557 self.pragma_once(rest[0].span);
558 } else if !self.macro_stack_pragma(rest, hash, interner, names) {
559 self.pass_through(body, hash, out);
560 }
561 } else if name == Some(names.include) || name == Some(names.include_next) {
562 self.include(rest, hash, name == Some(names.include_next), out, cx, names);
563 } else if name == Some(names.embed) {
564 self.embed(rest, hash, out, cx);
565 } else {
566 self.diagnostics.push(
567 Diagnostic::error("invalid preprocessing directive", first.span).with_code("E0332"),
568 );
569 }
570 }
571
572 fn macro_stack_pragma(
584 &mut self,
585 rest: &[PpToken],
586 at: Span,
587 interner: &mut Interner,
588 names: &Names,
589 ) -> bool {
590 let which = match rest.first().and_then(ident_of) {
591 Some(name) if name == names.push_macro => names.push_macro,
592 Some(name) if name == names.pop_macro => names.pop_macro,
593 _ => return false,
594 };
595 let word = if which == names.push_macro { "push_macro" } else { "pop_macro" };
596 let [_, open, text, close, extra @ ..] = rest else {
601 self.invalid_pragma(word, at);
602 return true;
603 };
604 if open.punct() != Some(Punct::LParen)
605 || text.kind != PpTokenKind::StringLit
606 || close.punct() != Some(Punct::RParen)
607 {
608 self.invalid_pragma(word, at);
609 return true;
610 }
611 self.extra_tokens(extra, "#pragma");
612 let Some(name) = identifier_in(*text, interner) else {
617 return true;
618 };
619 if which == names.push_macro {
620 self.macros.push_macro(name);
621 } else {
622 self.macros.pop_macro(name);
623 }
624 true
625 }
626
627 fn invalid_pragma(&mut self, word: &str, at: Span) {
628 self.diagnostics.push(
629 Diagnostic::error(format!("invalid `#pragma {word}` directive"), at).with_code("E0672"),
630 );
631 }
632
633 fn pragma_once(&mut self, at: Span) {
635 if self.stack.len() <= 1 {
640 self.diagnostics.push(
641 Diagnostic::warning("`#pragma once` in the main file", at).with_code("W0332"),
642 );
643 }
644 if let Some(frame) = self.stack.last() {
645 self.seen.insert(frame.id.clone(), Guard::Once);
646 }
647 }
648
649 fn skip(&self, id: &Path) -> bool {
651 match self.seen.get(id) {
652 Some(Guard::Once) => true,
653 Some(Guard::Macro(name)) => self.macros.is_defined(*name),
654 None => false,
655 }
656 }
657
658 fn pass_through(&mut self, body: &[PpToken], hash: Span, out: &mut Vec<Tok>) {
660 let _ = self;
661 out.push(Tok::synthetic(
662 PpTokenKind::Punct(Punct::Hash),
663 None,
664 TokenFlags::START_OF_LINE,
665 hash,
666 ));
667 for (at, token) in body.iter().copied().enumerate() {
672 let mut token = Tok::new(token);
673 if at == 0 {
674 token.flags = token.flags.without(TokenFlags::LEADING_SPACE);
675 }
676 out.push(token);
677 }
678 }
679
680 fn include(
682 &mut self,
683 rest: &[PpToken],
684 hash: Span,
685 is_next: bool,
686 out: &mut Vec<Tok>,
687 cx: &mut Context<'_>,
688 names: &Names,
689 ) {
690 let Some(header) = self.header_of(rest, hash, cx) else {
691 return;
692 };
693 let (form, relative_to, from) = self.where_to_look(&header, is_next, cx);
694 let found = cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from);
695 let Some(found) = found else {
696 let tried = cx.search.tried(&header.name, form, relative_to.as_deref(), from);
697 self.not_found(&header.name, hash, &tried, cx.search.missing_system());
698 return;
699 };
700 self.read(found, hash, out, cx, names);
701 }
702
703 fn not_found(&mut self, name: &str, at: Span, tried: &[PathBuf], why: Option<&str>) {
710 let where_looked = if tried.is_empty() && Path::new(name).is_absolute() {
714 "the name is an absolute path, so the search path was not used".to_owned()
715 } else if tried.is_empty() {
716 "the include search path is empty".to_owned()
717 } else {
718 let list: Vec<String> =
719 tried.iter().map(|d| d.to_string_lossy().into_owned()).collect();
720 format!("searched: {}", list.join(", "))
721 };
722 let mut said = Diagnostic::error(format!("`{name}` file not found"), at)
723 .with_code("E0341")
724 .note(where_looked, at);
725 if let Some(why) = why {
726 said = said.note(why, at);
727 }
728 self.diagnostics.push(said);
729 }
730
731 fn read(
738 &mut self,
739 found: Found,
740 at: Span,
741 out: &mut Vec<Tok>,
742 cx: &mut Context<'_>,
743 names: &Names,
744 ) {
745 let id = cx.fs.identity(&found.path);
746 if self.dep_ids.insert(id.clone()) {
751 let path = rucc_session::path_key(&found.path);
758 self.deps.push(Dependency { path, is_system: found.is_system });
759 }
760 if self.skip(&id) {
765 return;
766 }
767 if self.stack.len() >= cx.max_include_depth as usize {
768 let mut diagnostic = Diagnostic::error("`#include` nested too deeply", at)
769 .with_code("E0342")
770 .note("a header that includes itself with no include guard is the usual cause", at);
771 if let Some(outer) = self.stack.first().filter(|f| !f.at.is_dummy()) {
772 diagnostic = diagnostic.note("the outermost include is here", outer.at);
773 }
774 self.diagnostics.push(diagnostic);
775 return;
776 }
777 let added = cx.sources.add_shared(found.name.clone(), found.bytes.clone(), Some(at));
778 let file = match added {
779 Ok(file) => file,
780 Err(full) => {
781 self.diagnostics.push(Diagnostic::error(full.to_string(), at).with_code("E0344"));
782 return;
783 }
784 };
785 self.stack.push(Frame {
786 at,
787 dir: found.path.parent().map(Path::to_path_buf),
788 id,
789 path: found.path,
790 next: found.next,
791 });
792 self.process(file, out, cx, names);
793 self.stack.pop();
794 }
795
796 fn embed(&mut self, rest: &[PpToken], hash: Span, out: &mut Vec<Tok>, cx: &mut Context<'_>) {
798 let Some((header, params)) = self.embed_line(rest, hash, cx) else {
799 return;
800 };
801 let Some(found) = self.find(&header, false, cx) else {
802 self.diagnostics.push(
803 Diagnostic::error(format!("`{}` resource not found", header.name), hash)
804 .with_code("E0341")
805 .note("an `#embed` resource is looked for on the include path", hash),
806 );
807 return;
808 };
809 embed::tokens(found.bytes.as_slice(), ¶ms, hash, cx.interner, out);
814 }
815
816 fn embed_line(
818 &mut self,
819 rest: &[PpToken],
820 hash: Span,
821 cx: &mut Context<'_>,
822 ) -> Option<(Header, embed::Params)> {
823 if rest.is_empty() {
824 self.bad_header(hash);
825 return None;
826 }
827 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
828 let line = if line[0].kind == PpTokenKind::HeaderName {
834 line
835 } else {
836 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
837 self.diagnostics.append(&mut self.expander.take_diagnostics());
838 expanded
839 };
840 let Some(used) = embed::header_length(&line) else {
841 self.bad_header(line.first().map_or(hash, |t| t.report_span()));
842 return None;
843 };
844 let header = if line[0].kind == PpTokenKind::HeaderName {
845 header_from_token(spelling(line[0], cx.interner))
846 } else {
847 let spellings: Vec<&str> =
848 line[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
849 header_from_tokens(&spellings)
850 };
851 let Some(header) = header else {
852 self.bad_header(line[0].report_span());
853 return None;
854 };
855 let params = self.embed_params(&line[used..], hash, cx)?;
856 Some((header, params))
857 }
858
859 fn embed_params(
861 &mut self,
862 line: &[Tok],
863 at: Span,
864 cx: &mut Context<'_>,
865 ) -> Option<embed::Params> {
866 let Preprocessor { expander, macros, diagnostics, .. } = self;
867 let sources = &mut *cx.sources;
868 let mut expand = |toks: Vec<Tok>, interner: &mut Interner| {
869 expander.expand_toks(toks, macros, interner, sources)
870 };
871 let params = embed::parse(line, at, cx.interner, diagnostics, &mut expand);
872 self.diagnostics.append(&mut self.expander.take_diagnostics());
873 params
874 }
875
876 fn where_to_look(
887 &self,
888 header: &Header,
889 is_next: bool,
890 cx: &Context<'_>,
891 ) -> (IncludeForm, Option<PathBuf>, usize) {
892 let form = if header.angled { IncludeForm::Angled } else { IncludeForm::Quoted };
893 let frame = self.stack.last();
894 let from = if is_next {
895 frame.map_or(0, |f| f.next).max(cx.search.start(form))
896 } else {
897 cx.search.start(form)
898 };
899 let relative_to = if is_next { None } else { frame.and_then(|f| f.dir.clone()) };
900 (form, relative_to, from)
901 }
902
903 fn find(&self, header: &Header, is_next: bool, cx: &Context<'_>) -> Option<Found> {
905 let (form, relative_to, from) = self.where_to_look(header, is_next, cx);
906 cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from)
907 }
908
909 fn header_of(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>) -> Option<Header> {
911 if let Some(first) = rest.first().copied() {
912 if first.kind == PpTokenKind::HeaderName {
913 let text = first.value.map_or("", |v| cx.interner.resolve(v));
914 let header = header_from_token(text);
915 if header.is_none() {
916 self.bad_header(first.span);
917 }
918 self.extra_tokens(&rest[1..], "#include");
919 return header;
920 }
921 }
922 if rest.is_empty() {
926 self.bad_header(hash);
927 return None;
928 }
929 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
930 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
931 self.diagnostics.append(&mut self.expander.take_diagnostics());
932 let spellings: Vec<&str> = expanded.iter().map(|t| spelling(*t, cx.interner)).collect();
933 let header = header_from_tokens(&spellings);
934 if header.is_none() {
935 let at = expanded.first().map_or(hash, |t| t.report_span());
936 self.bad_header(at);
937 }
938 header
939 }
940
941 fn bad_operand(&mut self, tok: Tok, at: Span, interner: &Interner) {
943 self.diagnostics.push(
944 Diagnostic::error(
945 format!("expected an identifier as the operand of `{}`", spelling(tok, interner)),
946 at,
947 )
948 .with_code("E0345"),
949 );
950 }
951
952 fn bad_header(&mut self, at: Span) {
953 self.diagnostics.push(
954 Diagnostic::error("expected a file name in `<>` or `\"\"`", at).with_code("E0343"),
955 );
956 }
957
958 fn open(&mut self, span: Span, value: bool) {
960 let enclosing_live = self.live();
961 self.conds.push(Cond {
962 span,
963 live: enclosing_live && value,
964 taken: value,
965 enclosing_live,
966 seen_else: false,
967 });
968 }
969
970 fn elif(
971 &mut self,
972 name: Option<Symbol>,
973 rest: &[PpToken],
974 hash: Span,
975 cx: &mut Context<'_>,
976 names: &Names,
977 ) {
978 let Some(top) = self.conds.last() else {
979 self.stray("elif", hash);
980 return;
981 };
982 if top.seen_else {
983 self.diagnostics
984 .push(Diagnostic::error("`#elif` after `#else`", hash).with_code("E0333"));
985 return;
986 }
987 let (enclosing_live, already_taken) = (top.enclosing_live, top.taken);
990 let consider = enclosing_live && !already_taken;
991 let value = if !consider {
992 false
993 } else if name == Some(names.elif) {
994 self.eval(rest, hash, cx, names)
995 } else {
996 self.defined_check(rest, hash, name == Some(names.elifdef), names)
997 };
998 let top = self.conds.last_mut().expect("checked above and nothing popped");
999 top.live = consider && value;
1000 top.taken = already_taken || value;
1001 }
1002
1003 fn branch_else(&mut self, rest: &[PpToken], hash: Span) {
1004 let Some(top) = self.conds.last_mut() else {
1005 self.stray("else", hash);
1006 return;
1007 };
1008 if top.seen_else {
1009 self.diagnostics.push(Diagnostic::error("a second `#else`", hash).with_code("E0333"));
1010 return;
1011 }
1012 top.live = top.enclosing_live && !top.taken;
1013 top.taken = true;
1014 top.seen_else = true;
1015 let enclosing_live = top.enclosing_live;
1016 if enclosing_live {
1017 self.extra_tokens(rest, "#else");
1018 }
1019 }
1020
1021 fn endif(&mut self, rest: &[PpToken], hash: Span) {
1022 if self.conds.pop().is_none() {
1023 self.stray("endif", hash);
1024 return;
1025 }
1026 if self.live() {
1027 self.extra_tokens(rest, "#endif");
1028 }
1029 }
1030
1031 fn stray(&mut self, what: &str, hash: Span) {
1032 self.diagnostics
1033 .push(Diagnostic::error(format!("`#{what}` without `#if`"), hash).with_code("E0334"));
1034 }
1035
1036 fn extra_tokens(&mut self, rest: &[PpToken], what: &str) {
1041 if let Some(first) = rest.first() {
1042 self.diagnostics.push(
1043 Diagnostic::warning(format!("extra tokens after `{what}`"), first.span)
1044 .with_code("W0330"),
1045 );
1046 }
1047 }
1048
1049 fn eval(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>, names: &Names) -> bool {
1051 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
1052 let line = self.resolve_defined(line, cx.interner, names);
1059 let line = self.resolve_has(line, cx, names, Pass::Headers);
1064 let condition = Condition { defined: names.defined, report: cx.pedantic };
1065 let line =
1066 self.expander.expand_condition(line, &self.macros, cx.interner, cx.sources, condition);
1067 self.diagnostics.append(&mut self.expander.take_diagnostics());
1068 let line = self.resolve_defined(line, cx.interner, names);
1069 let line = self.resolve_has(line, cx, names, Pass::Rest);
1070 cond::evaluate(&line, cx.interner, &mut self.diagnostics, hash)
1071 }
1072
1073 fn resolve_has(
1078 &mut self,
1079 line: Vec<Tok>,
1080 cx: &mut Context<'_>,
1081 names: &Names,
1082 pass: Pass,
1083 ) -> Vec<Tok> {
1084 if !line.iter().any(|t| t.ident().is_some_and(|n| names.has.op(n).is_some())) {
1085 return line;
1086 }
1087 let mut out = Vec::with_capacity(line.len());
1088 let mut at = 0;
1089 while at < line.len() {
1090 let tok = line[at];
1091 let op = tok.ident().and_then(|n| names.has.op(n));
1092 let Some(op) = op.filter(|op| pass.answers(*op)) else {
1093 if pass == Pass::Text && op.is_some_and(Op::is_header) {
1094 self.outside_a_directive(tok, cx);
1095 }
1096 out.push(tok);
1097 at += 1;
1098 continue;
1099 };
1100 let Some((operand, after)) = arguments(&line, at + 1) else {
1101 if pass != Pass::Headers {
1105 self.diagnostics.push(
1106 Diagnostic::error(
1107 format!("expected `(` after `{}`", spelling(tok, cx.interner)),
1108 tok.report_span(),
1109 )
1110 .with_code("E0345"),
1111 );
1112 }
1113 out.push(tok);
1114 at += 1;
1115 continue;
1116 };
1117 at = after;
1118 let value = self.ask(op, operand, tok, cx);
1121 let sym = cx.interner.intern(&value.to_string());
1122 out.push(Tok::synthetic(PpTokenKind::Number, Some(sym), tok.flags, tok.report_span()));
1123 }
1124 out
1125 }
1126
1127 fn outside_a_directive(&mut self, tok: Tok, cx: &Context<'_>) {
1135 self.diagnostics.push(
1136 Diagnostic::error(
1137 format!(
1138 "`{}` used outside of a preprocessing directive",
1139 spelling(tok, cx.interner)
1140 ),
1141 tok.report_span(),
1142 )
1143 .with_code("E0350"),
1144 );
1145 }
1146
1147 fn ask(&mut self, op: Op, operand: &[Tok], tok: Tok, cx: &mut Context<'_>) -> u32 {
1149 let at = operand.first().map_or(tok.report_span(), |t| t.report_span());
1150 match op {
1151 Op::Include | Op::IncludeNext => {
1152 let spellings: Vec<&str> =
1153 operand.iter().map(|t| spelling(*t, cx.interner)).collect();
1154 let Some(header) = header_from_tokens(&spellings) else {
1155 self.bad_header(at);
1156 return 0;
1157 };
1158 u32::from(self.find(&header, op == Op::IncludeNext, cx).is_some())
1159 }
1160 Op::Embed => {
1161 let Some(used) = embed::header_length(operand) else {
1166 self.bad_header(at);
1167 return 0;
1168 };
1169 let header = if operand[0].kind == PpTokenKind::HeaderName {
1170 header_from_token(spelling(operand[0], cx.interner))
1171 } else {
1172 let spellings: Vec<&str> =
1173 operand[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
1174 header_from_tokens(&spellings)
1175 };
1176 let Some(header) = header else {
1177 self.bad_header(at);
1178 return 0;
1179 };
1180 let Some(params) = self.embed_params(&operand[used..], at, cx) else {
1185 return 0;
1186 };
1187 match self.find(&header, false, cx) {
1188 None => 0,
1189 Some(found) => {
1190 let taken = params.taken(found.bytes.as_slice().len() as u64);
1191 if taken == 0 { 2 } else { 1 }
1192 }
1193 }
1194 }
1195 Op::BuildingModule => {
1196 if attribute_name(operand, cx.interner).is_none() {
1197 self.bad_operand(tok, at, cx.interner);
1198 }
1199 0
1206 }
1207 Op::Table(kind) => {
1208 let Some(name) = attribute_name(operand, cx.interner) else {
1209 self.bad_operand(tok, at, cx.interner);
1210 return 0;
1211 };
1212 match kind {
1213 Kind::Attribute => rucc_gnu::has_attribute(name),
1214 Kind::CAttribute => rucc_gnu::has_c_attribute(name),
1215 Kind::Builtin => rucc_gnu::has_builtin(name),
1216 Kind::Feature => rucc_gnu::has_feature(name),
1217 Kind::Extension => rucc_gnu::has_extension(name),
1218 }
1219 }
1220 }
1221 }
1222
1223 fn resolve_defined(
1225 &mut self,
1226 line: Vec<Tok>,
1227 interner: &mut Interner,
1228 names: &Names,
1229 ) -> Vec<Tok> {
1230 if !line.iter().any(|t| t.ident() == Some(names.defined)) {
1231 return line;
1232 }
1233 let mut out = Vec::with_capacity(line.len());
1234 let mut at = 0;
1235 while at < line.len() {
1236 let tok = line[at];
1237 if tok.ident() != Some(names.defined) {
1238 out.push(tok);
1239 at += 1;
1240 continue;
1241 }
1242 let parenthesised = line.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1243 let name_at = if parenthesised { at + 2 } else { at + 1 };
1244 let name = line.get(name_at).and_then(|t| t.ident());
1245 let Some(name) = name else {
1246 self.diagnostics.push(
1247 Diagnostic::error("`defined` without a macro name", tok.report_span())
1248 .with_code("E0335"),
1249 );
1250 out.push(tok);
1251 at += 1;
1252 continue;
1253 };
1254 at = name_at + 1;
1255 if parenthesised {
1256 if line.get(at).is_some_and(|t| t.is(Punct::RParen)) {
1257 at += 1;
1258 } else {
1259 self.diagnostics.push(
1260 Diagnostic::error("expected `)` after `defined`", tok.report_span())
1261 .with_code("E0335"),
1262 );
1263 }
1264 }
1265 let value = self.macros.is_defined(name) || names.has.op(name).is_some();
1269 out.push(number(value, tok.flags, tok.report_span(), interner));
1270 }
1271 out
1272 }
1273
1274 fn defined_check(
1276 &mut self,
1277 rest: &[PpToken],
1278 hash: Span,
1279 want_defined: bool,
1280 names: &Names,
1281 ) -> bool {
1282 let Some(name) = rest.first().and_then(ident_of) else {
1283 self.diagnostics.push(
1284 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1285 .with_code("E0336"),
1286 );
1287 return false;
1288 };
1289 self.extra_tokens(&rest[1..], if want_defined { "#ifdef" } else { "#ifndef" });
1290 let defined = self.macros.is_defined(name) || names.has.op(name).is_some();
1291 defined == want_defined
1292 }
1293
1294 fn undef(&mut self, rest: &[PpToken], hash: Span, interner: &Interner) {
1295 let Some(name) = rest.first().and_then(ident_of) else {
1296 self.diagnostics.push(
1297 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1298 .with_code("E0336"),
1299 );
1300 return;
1301 };
1302 let text = interner.resolve(name);
1305 if text == "defined" || text.starts_with("__STDC_") {
1306 self.diagnostics.push(
1307 Diagnostic::error(format!("`{text}` cannot be undefined"), rest[0].span)
1308 .with_code("E0337"),
1309 );
1310 return;
1311 }
1312 self.macros.undef(name);
1313 self.extra_tokens(&rest[1..], "#undef");
1314 }
1315
1316 fn message(&mut self, rest: &[PpToken], hash: Span, fatal: bool, interner: &Interner) {
1318 let text = spell_line(rest, interner);
1319 let span = rest.first().map_or(hash, |t| t.span.to(last_span(rest)));
1320 let diag = if fatal {
1321 Diagnostic::error(text, span).with_code("E0338")
1322 } else {
1323 Diagnostic::warning(text, span).with_code("W0331")
1324 };
1325 self.diagnostics.push(diag);
1326 }
1327
1328 fn line(&mut self, rest: &[PpToken], hash: Span, at: usize, cx: &mut Context<'_>) {
1333 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
1334 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
1335 self.diagnostics.append(&mut self.expander.take_diagnostics());
1336 let interner = &mut *cx.interner;
1337
1338 let number_text = line
1339 .first()
1340 .filter(|t| t.kind == PpTokenKind::Number)
1341 .and_then(|t| t.value)
1342 .map(|v| interner.resolve(v));
1343 let Some(parsed) = number_text.and_then(|t| t.parse::<u64>().ok()) else {
1344 self.diagnostics.push(
1345 Diagnostic::error(
1346 "`#line` needs a decimal line number",
1347 line.first().map_or(hash, |t| t.report_span()),
1348 )
1349 .with_code("E0339"),
1350 );
1351 return;
1352 };
1353 if parsed == 0 || parsed > 2_147_483_647 {
1356 self.diagnostics.push(
1357 Diagnostic::error("`#line` number is out of range", line[0].report_span())
1358 .with_code("E0339"),
1359 );
1360 return;
1361 }
1362
1363 let mut file = None;
1364 if let Some(second) = line.get(1) {
1365 if second.kind == PpTokenKind::StringLit {
1366 file = second.value;
1367 } else {
1368 self.diagnostics.push(
1369 Diagnostic::error(
1370 "`#line` file name must be a string literal",
1371 second.report_span(),
1372 )
1373 .with_code("E0339"),
1374 );
1375 return;
1376 }
1377 }
1378 if let Some(extra) = line.get(2) {
1379 self.diagnostics.push(
1380 Diagnostic::warning("extra tokens after `#line`", extra.report_span())
1381 .with_code("W0330"),
1382 );
1383 }
1384 #[expect(
1385 clippy::cast_possible_truncation,
1386 reason = "the range check above keeps this inside i32, let alone u32"
1387 )]
1388 let number = parsed as u32;
1389 self.lines.push(LineDirective { span: hash, line: number, file, at });
1390 let name = file.map(|v| destringize(cx.interner.resolve(v)));
1391 cx.sources.set_presumed(hash.lo, number, name);
1392 }
1393
1394 fn line_marker(&mut self, body: &[PpToken], hash: Span, at: usize, cx: &mut Context<'_>) {
1411 let Some(number) = decimal(&body[0], cx.interner) else { return };
1412 let mut rest = &body[1..];
1413 let mut file = None;
1414 if let Some(first) = rest.first().filter(|t| t.kind == PpTokenKind::StringLit) {
1415 file = first.value;
1416 rest = &rest[1..];
1417 }
1418
1419 let (mut entering, mut leaving) = (false, false);
1420 for flag in rest {
1421 match decimal(flag, cx.interner) {
1422 Some(1) => entering = true,
1423 Some(2) => leaving = true,
1424 Some(3 | 4) => {}
1425 _ => {
1426 let text = spell_line(std::slice::from_ref(flag), cx.interner);
1427 self.diagnostics.push(
1428 Diagnostic::error(
1429 format!("invalid flag `{text}` in line directive"),
1430 flag.span,
1431 )
1432 .with_code("E0339"),
1433 );
1434 return;
1435 }
1436 }
1437 }
1438
1439 let name = file.map(|v| destringize(cx.interner.resolve(v)));
1440 if leaving {
1441 if let Some(name) = &name {
1442 if !self.leave_marker(name) {
1443 self.diagnostics.push(
1444 Diagnostic::warning(
1445 format!("file `{name}` linemarker ignored due to incorrect nesting"),
1446 last_span(body),
1447 )
1448 .with_code("W0330"),
1449 );
1450 return;
1451 }
1452 } else {
1453 self.markers.pop();
1454 }
1455 }
1456 if entering {
1457 let here = cx.sources.presumed(hash.lo).map(|loc| loc.name.to_owned());
1458 self.markers.push(here.unwrap_or_default());
1459 }
1460
1461 self.lines.push(LineDirective { span: hash, line: number, file, at });
1462 cx.sources.set_presumed(hash.lo, number, name);
1463 }
1464
1465 fn leave_marker(&mut self, name: &str) -> bool {
1479 if let Some(at) = self.markers.iter().rposition(|outer| outer == name) {
1480 self.markers.truncate(at);
1481 return true;
1482 }
1483 let found = self.stack.iter().rev().skip(1).any(|f| f.path.as_os_str() == name);
1486 if found {
1487 self.markers.clear();
1488 }
1489 found
1490 }
1491
1492 fn pragma_operator(
1498 &mut self,
1499 expanded: Vec<Tok>,
1500 out: &mut Vec<Tok>,
1501 interner: &mut Interner,
1502 names: &Names,
1503 ) {
1504 if !expanded.iter().any(|t| t.ident() == Some(names.pragma_op)) {
1505 out.extend(expanded);
1506 return;
1507 }
1508 let mut at = 0;
1509 let mut ends_a_line = false;
1514 while at < expanded.len() {
1515 let mut tok = expanded[at];
1516 if tok.ident() != Some(names.pragma_op) {
1517 if ends_a_line {
1518 tok.flags = tok.flags.with(TokenFlags::START_OF_LINE);
1519 ends_a_line = false;
1520 }
1521 out.push(tok);
1522 at += 1;
1523 continue;
1524 }
1525 let open = expanded.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1526 let text = expanded.get(at + 2).filter(|t| t.kind == PpTokenKind::StringLit);
1527 let close = expanded.get(at + 3).is_some_and(|t| t.is(Punct::RParen));
1528 let (Some(text), true, true) = (text, open, close) else {
1529 self.diagnostics.push(
1530 Diagnostic::error("`_Pragma` takes a single string literal", tok.report_span())
1531 .with_code("E0340"),
1532 );
1533 out.push(tok);
1534 at += 1;
1535 continue;
1536 };
1537 let literal = text.value.map(|v| interner.resolve(v)).unwrap_or_default();
1538 let body = destringize(literal);
1539 self.emit_pragma(&body, tok, out, interner, names);
1540 ends_a_line = true;
1541 at += 4;
1542 }
1543 }
1544
1545 fn emit_pragma(
1547 &mut self,
1548 body: &str,
1549 at: Tok,
1550 out: &mut Vec<Tok>,
1551 interner: &mut Interner,
1552 names: &Names,
1553 ) {
1554 let span = at.report_span();
1555 let (tokens, diagnostics) = tokenize(body.as_bytes(), 0, Options::new(), interner);
1556 self.diagnostics.extend(
1559 diagnostics
1560 .into_iter()
1561 .map(|d| Diagnostic::new(d.severity, d.message, span).with_code("E0340")),
1562 );
1563 let tokens: Vec<PpToken> = tokens.into_iter().filter(|t| !t.is_eof()).collect();
1564 if self.macro_stack_pragma(&tokens, span, interner, names) {
1568 return;
1569 }
1570 out.push(Tok::synthetic(
1571 PpTokenKind::Punct(Punct::Hash),
1572 None,
1573 TokenFlags::START_OF_LINE,
1574 span,
1575 ));
1576 out.push(Tok::synthetic(PpTokenKind::Ident, Some(names.pragma), TokenFlags::EMPTY, span));
1577 for (at, t) in tokens.into_iter().enumerate() {
1581 let spaced = at == 0 || t.flags.has(TokenFlags::LEADING_SPACE);
1584 let flags = if spaced {
1585 TokenFlags::EMPTY.with(TokenFlags::LEADING_SPACE)
1586 } else {
1587 TokenFlags::EMPTY
1588 };
1589 out.push(Tok::synthetic(t.kind, t.value, flags, span));
1590 }
1591 }
1592}
1593
1594fn guard_opener(body: &[PpToken], names: &Names) -> Option<Symbol> {
1599 let name = ident_of(body.first()?)?;
1600 let rest = &body[1..];
1601 if name == names.ifndef {
1602 let [only] = rest else {
1603 return None;
1604 };
1605 return ident_of(only);
1606 }
1607 if name != names.r#if {
1608 return None;
1609 }
1610 let [bang, defined, tail @ ..] = rest else {
1611 return None;
1612 };
1613 if bang.punct() != Some(Punct::Bang) || ident_of(defined) != Some(names.defined) {
1614 return None;
1615 }
1616 match tail {
1617 [only] => ident_of(only),
1618 [open, only, close]
1619 if open.punct() == Some(Punct::LParen) && close.punct() == Some(Punct::RParen) =>
1620 {
1621 ident_of(only)
1622 }
1623 _ => None,
1624 }
1625}
1626
1627fn is_include(name: Option<Symbol>, names: &Names) -> bool {
1629 name == Some(names.include) || name == Some(names.include_next) || name == Some(names.embed)
1630}
1631
1632fn is_directive(tok: PpToken) -> bool {
1634 tok.flags.has(TokenFlags::START_OF_LINE) && tok.punct() == Some(Punct::Hash)
1635}
1636
1637fn ident_of(tok: &PpToken) -> Option<Symbol> {
1638 match tok.kind {
1639 PpTokenKind::Ident => tok.value,
1640 _ => None,
1641 }
1642}
1643
1644fn last_span(tokens: &[PpToken]) -> Span {
1645 tokens.last().map_or(Span::DUMMY, |t| t.span)
1646}
1647
1648fn decimal(tok: &PpToken, interner: &Interner) -> Option<u32> {
1654 if tok.kind != PpTokenKind::Number {
1655 return None;
1656 }
1657 let text = interner.resolve(tok.value?);
1658 if text.is_empty() || !text.bytes().all(|b| b.is_ascii_digit()) {
1659 return None;
1660 }
1661 text.parse::<u32>().ok().filter(|n| *n <= 2_147_483_647)
1664}
1665
1666fn number(value: bool, flags: TokenFlags, span: Span, interner: &mut Interner) -> Tok {
1668 let sym = interner.intern(if value { "1" } else { "0" });
1669 Tok::synthetic(PpTokenKind::Number, Some(sym), flags, span)
1670}
1671
1672fn spell_line(tokens: &[PpToken], interner: &Interner) -> String {
1674 let mut out = String::new();
1675 for (index, tok) in tokens.iter().enumerate() {
1676 if index > 0 && tok.flags.has(TokenFlags::LEADING_SPACE) {
1677 out.push(' ');
1678 }
1679 match tok.value {
1680 Some(sym) => out.push_str(interner.resolve(sym)),
1681 None => {
1682 if let Some(p) = tok.punct() {
1683 out.push_str(p.as_str());
1684 }
1685 }
1686 }
1687 }
1688 out
1689}
1690
1691fn identifier_in(text: PpToken, interner: &mut Interner) -> Option<Symbol> {
1696 let literal = interner.resolve(text.value?).to_string();
1697 let (tokens, _) = tokenize(destringize(&literal).as_bytes(), 0, Options::new(), interner);
1698 let mut real = tokens.into_iter().filter(|t| !t.is_eof());
1699 let first = real.next()?;
1700 if first.kind != PpTokenKind::Ident || real.next().is_some() {
1701 return None;
1702 }
1703 first.value
1704}
1705
1706fn destringize(literal: &str) -> String {
1711 let body = literal
1712 .trim_start_matches(['L', 'u', 'U', '8'])
1713 .strip_prefix('"')
1714 .and_then(|s| s.strip_suffix('"'))
1715 .unwrap_or(literal);
1716 let mut out = String::with_capacity(body.len());
1717 let mut chars = body.chars();
1718 while let Some(c) = chars.next() {
1719 if c != '\\' {
1720 out.push(c);
1721 continue;
1722 }
1723 match chars.next() {
1724 Some('"') => out.push('"'),
1725 Some('\\') => out.push('\\'),
1726 Some(other) => {
1727 out.push('\\');
1728 out.push(other);
1729 }
1730 None => out.push('\\'),
1731 }
1732 }
1733 out
1734}
1735
1736fn arguments(line: &[Tok], at: usize) -> Option<(&[Tok], usize)> {
1742 if !line.get(at)?.is(Punct::LParen) {
1743 return None;
1744 }
1745 let mut depth = 1u32;
1746 let mut end = at + 1;
1747 while end < line.len() {
1748 if line[end].is(Punct::LParen) {
1749 depth += 1;
1750 } else if line[end].is(Punct::RParen) {
1751 depth -= 1;
1752 if depth == 0 {
1753 return Some((&line[at + 1..end], end + 1));
1754 }
1755 }
1756 end += 1;
1757 }
1758 None
1759}
1760
1761fn attribute_name<'i>(operand: &[Tok], interner: &'i Interner) -> Option<&'i str> {
1767 let name = match operand {
1768 [one] => one,
1769 [_, scope, name] if scope.is(Punct::ColonColon) => name,
1770 _ => return None,
1771 };
1772 name.ident().map(|sym| interner.resolve(sym))
1773}
1774
1775#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1782enum Pass {
1783 Headers,
1785 Rest,
1788 Text,
1790}
1791
1792impl Pass {
1793 fn answers(self, op: Op) -> bool {
1795 match self {
1796 Pass::Headers => op.is_header(),
1797 Pass::Rest => true,
1798 Pass::Text => !op.is_header(),
1799 }
1800 }
1801}
1802
1803#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1805enum Op {
1806 Include,
1808 IncludeNext,
1810 Embed,
1813 BuildingModule,
1815 Table(Kind),
1817}
1818
1819impl Op {
1820 fn is_header(self) -> bool {
1822 matches!(self, Op::Include | Op::IncludeNext | Op::Embed)
1823 }
1824}
1825
1826struct HasOps {
1831 ops: [(Symbol, Op); 9],
1832 range: (Symbol, Symbol),
1839}
1840
1841impl HasOps {
1842 fn new(interner: &mut Interner) -> HasOps {
1843 let ops = [
1844 (interner.intern("__has_include"), Op::Include),
1845 (interner.intern("__has_include_next"), Op::IncludeNext),
1846 (interner.intern("__has_embed"), Op::Embed),
1847 (interner.intern("__has_attribute"), Op::Table(Kind::Attribute)),
1848 (interner.intern("__has_c_attribute"), Op::Table(Kind::CAttribute)),
1849 (interner.intern("__has_builtin"), Op::Table(Kind::Builtin)),
1850 (interner.intern("__has_feature"), Op::Table(Kind::Feature)),
1851 (interner.intern("__has_extension"), Op::Table(Kind::Extension)),
1852 (interner.intern("__building_module"), Op::BuildingModule),
1853 ];
1854 let mut range = (ops[0].0, ops[0].0);
1855 for &(sym, _) in &ops {
1856 range = (range.0.min(sym), range.1.max(sym));
1857 }
1858 HasOps { ops, range }
1859 }
1860
1861 #[inline]
1863 fn op(&self, name: Symbol) -> Option<Op> {
1864 if name < self.range.0 || name > self.range.1 {
1865 return None;
1866 }
1867 self.ops.iter().find(|(sym, _)| *sym == name).map(|(_, op)| *op)
1868 }
1869}
1870
1871struct Names {
1877 define: Symbol,
1878 undef: Symbol,
1879 r#if: Symbol,
1880 ifdef: Symbol,
1881 ifndef: Symbol,
1882 elif: Symbol,
1883 elifdef: Symbol,
1884 elifndef: Symbol,
1885 r#else: Symbol,
1886 endif: Symbol,
1887 line: Symbol,
1888 error: Symbol,
1889 warning: Symbol,
1890 pragma: Symbol,
1891 include: Symbol,
1892 include_next: Symbol,
1893 embed: Symbol,
1894 defined: Symbol,
1895 once: Symbol,
1896 push_macro: Symbol,
1897 pop_macro: Symbol,
1898 pragma_op: Symbol,
1899 has: HasOps,
1900}
1901
1902impl Names {
1903 fn new(interner: &mut Interner) -> Names {
1904 Names {
1905 define: interner.intern("define"),
1906 undef: interner.intern("undef"),
1907 r#if: interner.intern("if"),
1908 ifdef: interner.intern("ifdef"),
1909 ifndef: interner.intern("ifndef"),
1910 elif: interner.intern("elif"),
1911 elifdef: interner.intern("elifdef"),
1912 elifndef: interner.intern("elifndef"),
1913 r#else: interner.intern("else"),
1914 endif: interner.intern("endif"),
1915 line: interner.intern("line"),
1916 error: interner.intern("error"),
1917 warning: interner.intern("warning"),
1918 pragma: interner.intern("pragma"),
1919 include: interner.intern("include"),
1920 include_next: interner.intern("include_next"),
1921 embed: interner.intern("embed"),
1922 defined: interner.intern("defined"),
1923 once: interner.intern("once"),
1924 push_macro: interner.intern("push_macro"),
1925 pop_macro: interner.intern("pop_macro"),
1926 pragma_op: interner.intern("_Pragma"),
1927 has: HasOps::new(interner),
1928 }
1929 }
1930}
1931
1932#[cfg(test)]
1933mod tests {
1934 use rucc_diag::{Severity, SourceMap};
1935 use rucc_session::{MemoryFileSystem, SearchPath};
1936
1937 use super::*;
1938 use rucc_session::Std;
1939
1940 use crate::predef::Timestamp;
1941
1942 fn slashes(text: &str) -> String {
1950 text.replace("\\\\", "/").replace('\\', "/")
1951 }
1952
1953 struct Run {
1954 interner: Interner,
1955 sources: SourceMap,
1956 fs: MemoryFileSystem,
1957 search: SearchPath,
1958 pp: Preprocessor,
1959 pedantic: bool,
1961 }
1962
1963 impl Run {
1964 fn new() -> Run {
1965 Run {
1966 interner: Interner::new(),
1967 sources: SourceMap::new(),
1968 fs: MemoryFileSystem::new(),
1969 search: SearchPath::new(),
1970 pp: Preprocessor::new(),
1971 pedantic: false,
1972 }
1973 }
1974
1975 fn pedantic() -> Run {
1977 Run { pedantic: true, ..Run::new() }
1978 }
1979
1980 fn mapping(map: &[(&str, &str)]) -> Run {
1982 let mut list = PrefixMap::new();
1983 for (old, new) in map {
1984 list.push(*old, *new);
1985 }
1986 Run { pp: Preprocessor::with_prefix_map(list), ..Run::new() }
1987 }
1988
1989 fn file(&mut self, path: &str, contents: &str) {
1991 self.fs.insert(path, contents.as_bytes().to_vec());
1992 }
1993
1994 fn bytes(&mut self, path: &str, contents: &[u8]) {
1997 self.fs.insert(path, contents.to_vec());
1998 }
1999
2000 fn dir(&mut self, path: &str) {
2002 self.search.push_bracket(path);
2003 }
2004
2005 fn predefine(&mut self, triple: &str, opts: &Predef) {
2007 let target = TargetInfo::new(triple.parse().expect("a supported triple"));
2008 let mut cx =
2009 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
2010 self.pp.predefine(&target, opts, &mut cx).expect("the map has room");
2011 }
2012
2013 fn go(&mut self, src: &str) -> String {
2015 self.go_named("/main.c", src)
2016 }
2017
2018 fn raw(&mut self, src: &str) -> Vec<Tok> {
2020 let file = self.sources.add("/main.c", src.as_bytes().to_vec()).expect("room");
2021 let pedantic = self.pedantic;
2022 let mut cx =
2023 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
2024 cx.pedantic = pedantic;
2025 self.pp.run(file, &mut cx)
2026 }
2027
2028 fn preinclude(&mut self, files: &[Preinclude]) -> String {
2030 let mut out = Vec::new();
2031 {
2032 let mut cx =
2033 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
2034 self.pp.preinclude(files, &mut out, &mut cx).expect("the map has room");
2035 }
2036 self.spell(&out)
2037 }
2038
2039 fn go_named(&mut self, path: &str, src: &str) -> String {
2041 let file = self.sources.add(path, src.as_bytes().to_vec()).expect("the map has room");
2042 let pedantic = self.pedantic;
2043 let out = {
2044 let mut cx =
2045 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
2046 cx.pedantic = pedantic;
2047 self.pp.run(file, &mut cx)
2048 };
2049 self.spell(&out)
2050 }
2051
2052 fn spell(&self, out: &[Tok]) -> String {
2054 let mut text = String::new();
2055 for (at, tok) in out.iter().enumerate() {
2056 let spaced = tok.flags.has(TokenFlags::LEADING_SPACE)
2057 || tok.flags.has(TokenFlags::START_OF_LINE);
2058 if at > 0 && spaced {
2059 text.push(' ');
2060 }
2061 match tok.kind {
2062 PpTokenKind::Punct(p) => text.push_str(p.as_str()),
2063 _ => text.push_str(
2064 self.interner.resolve(tok.value.expect("every non-punctuator interns")),
2065 ),
2066 }
2067 }
2068 text
2069 }
2070
2071 fn files(&self) -> usize {
2075 self.sources.files().len()
2076 }
2077
2078 fn messages(&mut self) -> Vec<String> {
2079 self.pp.take_diagnostics().into_iter().map(|d| d.message).collect()
2080 }
2081
2082 fn severities(&mut self) -> Vec<Severity> {
2083 self.pp.diagnostics().iter().map(|d| d.severity).collect()
2084 }
2085 }
2086
2087 fn clean(src: &str) -> String {
2088 let mut run = Run::new();
2089 let text = run.go(src);
2090 assert!(run.messages().is_empty(), "expected no diagnostics from {src:?}");
2091 text
2092 }
2093
2094 #[test]
2095 fn a_taken_branch_is_kept_and_the_other_is_not() {
2096 assert_eq!(clean("#if 1\nyes\n#else\nno\n#endif\n"), "yes");
2097 assert_eq!(clean("#if 0\nyes\n#else\nno\n#endif\n"), "no");
2098 }
2099
2100 #[test]
2101 fn ifdef_and_ifndef_ask_the_macro_table() {
2102 assert_eq!(clean("#define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
2103 assert_eq!(clean("#ifdef F\nyes\n#endif\n"), "");
2104 assert_eq!(clean("#ifndef F\nyes\n#endif\n"), "yes");
2105 assert_eq!(clean("#define F 1\n#if 0\na\n#elifdef F\nb\n#endif\n"), "b");
2107 assert_eq!(clean("#if 0\na\n#elifndef F\nb\n#endif\n"), "b");
2108 }
2109
2110 #[test]
2111 fn only_the_first_true_branch_of_a_chain_is_taken() {
2112 assert_eq!(clean("#if 0\na\n#elif 1\nb\n#elif 1\nc\n#else\nd\n#endif\n"), "b");
2113 assert_eq!(clean("#if 0\na\n#elif 0\nb\n#else\nc\n#endif\n"), "c");
2114 }
2115
2116 #[test]
2117 fn a_branch_after_one_that_was_taken_is_not_evaluated() {
2118 assert_eq!(clean("#if 1\na\n#elif 1/0\nb\n#endif\n"), "a");
2121 }
2122
2123 #[test]
2124 fn a_skipped_region_is_not_read_for_anything_but_nesting() {
2125 let src = "#if 0\nthis is not C at all\n#frobnicate\n#define\n#if 1\ninner\n#endif\n#endif\nafter\n";
2127 assert_eq!(clean(src), "after");
2128 }
2129
2130 #[test]
2131 fn nesting_inside_a_dead_branch_stays_balanced() {
2132 let src = "#if 0\n#ifdef X\na\n#else\nb\n#endif\n#else\nc\n#endif\n";
2133 assert_eq!(clean(src), "c");
2134 }
2135
2136 #[test]
2137 fn defined_works_in_both_spellings_and_before_expansion() {
2138 assert_eq!(clean("#define F 0\n#if defined F\nyes\n#endif\n"), "yes");
2139 assert_eq!(clean("#define F 0\n#if defined(F)\nyes\n#endif\n"), "yes");
2140 assert_eq!(clean("#if defined(F)\nyes\n#endif\n"), "");
2141 assert_eq!(clean("#define F 0\n#if defined F && !F\nyes\n#endif\n"), "yes");
2144 }
2145
2146 #[test]
2147 fn a_macro_may_write_the_defined_operator_itself() {
2148 assert_eq!(clean("#define F 0\n#define D defined F\n#if D\nyes\n#endif\n"), "yes");
2152 assert_eq!(clean("#define F 0\n#define D defined(F)\n#if D\nyes\n#endif\n"), "yes");
2153 assert_eq!(clean("#define D defined(F)\n#if D\nyes\n#else\nno\n#endif\n"), "no");
2154 }
2155
2156 #[test]
2157 fn the_name_a_macro_wrote_the_defined_operator_about_is_not_expanded() {
2158 assert_eq!(clean("#define F 1\n#define D defined(F)\n#if D\nyes\n#endif\n"), "yes");
2163 assert_eq!(clean("#define F\n#define D defined(F)\n#if D\nyes\n#endif\n"), "yes");
2167 let src = "#define MARK_lrotl\n#define HAVE(n) defined(MARK_ ## n)\n\
2169 #if HAVE(lrotl)\nyes\n#endif\n";
2170 assert_eq!(clean(src), "yes");
2171 }
2172
2173 #[test]
2174 fn a_defined_a_macro_wrote_is_reported_under_pedantic() {
2175 let mut run = Run::pedantic();
2176 assert_eq!(run.go("#define F 1\n#define D defined(F)\n#if D\nyes\n#endif\n"), "yes");
2177 assert_eq!(run.messages(), vec!["this use of `defined` may not be portable".to_owned()]);
2178 let mut run = Run::pedantic();
2181 assert_eq!(run.go("#define F 1\n#if defined(F)\nyes\n#endif\n"), "yes");
2182 assert!(run.messages().is_empty());
2183 assert_eq!(clean("#define F 1\n#define D defined(F)\n#if D\nyes\n#endif\n"), "yes");
2186 }
2187
2188 #[test]
2189 fn a_defined_a_macro_wrote_badly_is_still_an_error() {
2190 let mut run = Run::new();
2194 run.go("#define D defined\n#if D\nyes\n#endif\n");
2195 assert_eq!(run.messages(), vec!["`defined` without a macro name".to_owned()]);
2196 let mut run = Run::new();
2199 run.go("#define D defined(1)\n#if D\nyes\n#endif\n");
2200 assert_eq!(run.messages()[0], "`defined` without a macro name");
2201 let mut run = Run::new();
2202 run.go("#define D defined(F\n#if D\nyes\n#endif\n");
2203 assert_eq!(run.messages(), vec!["expected `)` after `defined`".to_owned()]);
2204 let mut run = Run::new();
2209 run.go("#define F 0\n#define D(x) defined(x)\n#if D(F)\nyes\n#endif\n");
2210 assert_eq!(run.messages()[0], "`defined` without a macro name");
2211 }
2212
2213 #[test]
2214 fn an_identifier_that_survived_expansion_is_zero() {
2215 assert_eq!(clean("#if NOT_DEFINED_ANYWHERE\nyes\n#else\nno\n#endif\n"), "no");
2216 assert_eq!(clean("#if !NOT_DEFINED_ANYWHERE\nyes\n#endif\n"), "yes");
2217 }
2218
2219 #[test]
2220 fn short_circuiting_keeps_a_guarded_expression_safe() {
2221 assert_eq!(clean("#if defined(F) && 1/F\nyes\n#else\nno\n#endif\n"), "no");
2224 assert_eq!(clean("#if 1 ? 2 : 1/0\nyes\n#endif\n"), "yes");
2225 }
2226
2227 #[test]
2228 fn the_operators_have_the_precedence_they_do_in_c() {
2229 assert_eq!(clean("#if 1 + 2 * 3 == 7\nyes\n#endif\n"), "yes");
2230 assert_eq!(clean("#if (1 + 2) * 3 == 9\nyes\n#endif\n"), "yes");
2231 assert_eq!(clean("#if 1 << 4 == 16\nyes\n#endif\n"), "yes");
2232 assert_eq!(clean("#if -8 / 3 == -2\nyes\n#endif\n"), "yes");
2233 assert_eq!(clean("#if (0xff & 0x0f) == 15\nyes\n#endif\n"), "yes");
2234 }
2235
2236 #[test]
2237 fn an_unsigned_operand_makes_the_whole_comparison_unsigned() {
2238 assert_eq!(clean("#if -1 < 0u\nyes\n#else\nno\n#endif\n"), "no");
2242 assert_eq!(clean("#if -1 < 0\nyes\n#else\nno\n#endif\n"), "yes");
2243 }
2244
2245 #[test]
2246 fn character_constants_evaluate() {
2247 assert_eq!(clean("#if 'A' == 65\nyes\n#endif\n"), "yes");
2248 assert_eq!(clean("#if '\\n' == 10\nyes\n#endif\n"), "yes");
2249 }
2250
2251 #[test]
2252 fn a_macro_is_expanded_before_the_expression_is_evaluated() {
2253 assert_eq!(clean("#define V 3\n#if V > 2\nyes\n#endif\n"), "yes");
2254 assert_eq!(clean("#define M(a) ((a) * 2)\n#if M(3) == 6\nyes\n#endif\n"), "yes");
2255 }
2256
2257 #[test]
2258 fn an_invocation_may_span_lines_within_a_run_of_text() {
2259 assert_eq!(clean("#define M(a, b) a + b\nM(1,\n2)\n"), "1 + 2");
2260 }
2261
2262 #[test]
2263 fn undef_removes_a_definition() {
2264 assert_eq!(clean("#define F 1\n#undef F\n#ifdef F\nyes\n#else\nno\n#endif\n"), "no");
2265 assert_eq!(clean("#undef NEVER_DEFINED\nok\n"), "ok");
2268 }
2269
2270 #[test]
2271 fn some_names_cannot_be_undefined() {
2272 let mut run = Run::new();
2273 run.go("#undef defined\n");
2274 assert_eq!(run.messages(), vec!["`defined` cannot be undefined".to_owned()]);
2275 }
2276
2277 #[test]
2278 fn error_reports_the_rest_of_the_line() {
2279 let mut run = Run::new();
2280 run.go("#if 0\n#error not this one\n#else\n#error unsupported target\n#endif\n");
2281 assert_eq!(run.messages(), vec!["unsupported target".to_owned()]);
2282 }
2283
2284 #[test]
2285 fn warning_is_a_warning() {
2286 let mut run = Run::new();
2287 run.go("#warning this is fine\n");
2288 assert_eq!(run.severities(), vec![Severity::Warning]);
2289 assert_eq!(run.messages(), vec!["this is fine".to_owned()]);
2290 }
2291
2292 #[test]
2293 fn an_unterminated_conditional_is_reported() {
2294 let mut run = Run::new();
2295 assert_eq!(run.go("#if 1\nyes\n"), "yes");
2296 assert_eq!(run.messages(), vec!["unterminated `#if`".to_owned()]);
2297 }
2298
2299 #[test]
2300 fn a_conditional_without_an_if_is_reported() {
2301 let mut run = Run::new();
2302 run.go("#endif\n");
2303 assert_eq!(run.messages(), vec!["`#endif` without `#if`".to_owned()]);
2304
2305 let mut run = Run::new();
2306 run.go("#if 1\n#else\n#else\n#endif\n");
2307 assert_eq!(run.messages(), vec!["a second `#else`".to_owned()]);
2308
2309 let mut run = Run::new();
2310 run.go("#if 1\n#else\n#elif 1\n#endif\n");
2311 assert_eq!(run.messages(), vec!["`#elif` after `#else`".to_owned()]);
2312 }
2313
2314 #[test]
2315 fn tokens_after_endif_are_a_warning_rather_than_an_error() {
2316 let mut run = Run::new();
2319 assert_eq!(run.go("#if 1\nyes\n#endif FOO\n"), "yes");
2320 assert_eq!(run.severities(), vec![Severity::Warning]);
2321 assert_eq!(run.messages(), vec!["extra tokens after `#endif`".to_owned()]);
2322 }
2323
2324 #[test]
2325 fn the_null_directive_does_nothing() {
2326 assert_eq!(clean("#\na\n#\nb\n"), "a b");
2327 }
2328
2329 #[test]
2330 fn an_unknown_directive_is_an_error_when_the_region_is_live() {
2331 let mut run = Run::new();
2332 run.go("#frobnicate\n");
2333 assert_eq!(run.messages(), vec!["invalid preprocessing directive".to_owned()]);
2334 }
2335
2336 #[test]
2337 fn line_is_recorded_for_the_source_map() {
2338 let mut run = Run::new();
2339 run.go("#line 42 \"other.c\"\n");
2340 assert!(run.messages().is_empty());
2341 let recorded = run.pp.line_directives();
2342 assert_eq!(recorded.len(), 1);
2343 assert_eq!(recorded[0].line, 42);
2344 let file = recorded[0].file.expect("a file name was given");
2345 assert_eq!(run.interner.resolve(file), "\"other.c\"");
2346 }
2347
2348 #[test]
2349 fn line_moves_what_line_and_file_the_lines_after_it_are_on() {
2350 let mut run = Run::new();
2351 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2352 assert_eq!(run.go("#line 1000\n__LINE__ __FILE__\n__LINE__\n"), "1000 \"/main.c\" 1001");
2353 }
2354
2355 #[test]
2356 fn a_line_marker_moves_the_lines_after_it_the_way_line_does() {
2357 let mut run = Run::new();
2358 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2359 assert_eq!(run.go("# 200 \"xyz\"\n__FILE__ __LINE__\n"), "\"xyz\" 200");
2360 }
2361
2362 #[test]
2363 fn a_line_marker_with_no_name_leaves_the_name_alone() {
2364 let mut run = Run::new();
2365 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2366 assert_eq!(run.go("# 20\n__FILE__ __LINE__\n"), "\"/main.c\" 20");
2367 }
2368
2369 #[test]
2370 fn a_line_marker_may_say_line_zero() {
2371 let mut run = Run::new();
2374 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2375 assert_eq!(run.go("# 0 \"xyz\"\n__LINE__\n"), "0");
2376 }
2377
2378 #[test]
2379 fn entering_and_returning_are_a_nesting_the_marker_flags_keep() {
2380 let mut run = Run::new();
2381 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2382 let text =
2383 run.go("# 200 \"xyz\" 1\n__FILE__\n# 5 \"/main.c\" 2\n__FILE__ __LINE__\n# 9 3 4\n");
2384 assert_eq!(text, "\"xyz\" \"/main.c\" 5");
2385 assert!(run.messages().is_empty());
2386 }
2387
2388 #[test]
2389 fn returning_to_a_file_nothing_was_ever_in_is_ignored() {
2390 let mut run = Run::new();
2391 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2392 assert_eq!(run.go("# 200 \"xyz\" 2 3\n__FILE__ __LINE__\n"), "\"/main.c\" 2");
2393 assert_eq!(
2394 run.messages(),
2395 vec!["file `xyz` linemarker ignored due to incorrect nesting".to_owned()]
2396 );
2397 }
2398
2399 #[test]
2400 fn a_flag_that_is_not_one_of_the_four_is_an_error() {
2401 let mut run = Run::new();
2402 run.go("# 20 \"a\" 7\n");
2403 assert_eq!(run.messages(), vec!["invalid flag `7` in line directive".to_owned()]);
2404 }
2405
2406 #[test]
2407 fn a_hash_and_something_that_is_not_a_line_number_is_still_an_unknown_directive() {
2408 let mut run = Run::new();
2410 run.go("# 1.5 \"a\"\n");
2411 assert_eq!(run.messages(), vec!["invalid preprocessing directive".to_owned()]);
2412 }
2413
2414 #[test]
2415 fn a_name_on_the_directive_is_the_name_from_there_on() {
2416 let mut run = Run::new();
2417 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2418 assert_eq!(run.go("#line 7 \"gen.y\"\n__FILE__ __LINE__\n"), "\"gen.y\" 7");
2419 }
2420
2421 #[test]
2422 fn a_directive_with_no_name_keeps_the_one_already_in_force() {
2423 let mut run = Run::new();
2424 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2425 assert_eq!(run.go("#line 7 \"gen.y\"\n#line 20\n__FILE__ __LINE__\n"), "\"gen.y\" 20");
2426 }
2427
2428 #[test]
2429 fn the_number_is_expanded_first_because_line_plus_one_is_real_code() {
2430 let mut run = Run::new();
2431 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2432 assert_eq!(run.go("#define WHERE 300\n#line WHERE\n__LINE__\n"), "300");
2433 }
2434
2435 #[test]
2436 fn a_directive_in_a_header_does_not_move_the_file_that_included_it() {
2437 let mut run = Run::new();
2438 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2439 run.file("/h.h", "#line 500\n__LINE__\n");
2440 run.dir("/");
2441 assert_eq!(run.go("#include <h.h>\n__LINE__\n"), "500 2");
2442 }
2443
2444 #[test]
2445 fn extra_tokens_after_the_file_name_are_a_warning_and_not_an_error() {
2446 let mut run = Run::new();
2447 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2448 assert_eq!(run.go("#line 7 \"gen.y\" and more\n__LINE__\n"), "7");
2449 assert_eq!(run.messages(), vec!["extra tokens after `#line`".to_owned()]);
2450 }
2451
2452 #[test]
2453 fn a_line_number_out_of_range_is_refused() {
2454 let mut run = Run::new();
2455 run.go("#line 0\n");
2456 assert_eq!(run.messages(), vec!["`#line` number is out of range".to_owned()]);
2457
2458 let mut run = Run::new();
2459 run.go("#line notanumber\n");
2460 assert_eq!(run.messages(), vec!["`#line` needs a decimal line number".to_owned()]);
2461 }
2462
2463 #[test]
2464 fn a_pragma_passes_through_unchanged() {
2465 assert_eq!(clean("#pragma pack(1)\nint x;\n"), "#pragma pack(1) int x;");
2466 }
2467
2468 #[test]
2473 fn the_space_between_the_hash_and_the_word_comes_off_a_pragma_that_is_indented() {
2474 assert_eq!(clean("#if 1\n# pragma pack(1)\n#endif\n"), "#pragma pack(1)");
2475 assert_eq!(clean("# pragma pack( 1 )\n"), "#pragma pack( 1 )", "the rest is kept");
2476 }
2477
2478 #[test]
2479 fn the_pragma_operator_becomes_a_pragma() {
2480 assert_eq!(
2481 clean("_Pragma(\"GCC visibility push(default)\")\nint x;\n"),
2482 "#pragma GCC visibility push(default) int x;"
2483 );
2484 }
2485
2486 #[test]
2487 fn the_pragma_operator_works_from_inside_a_macro() {
2488 let src = "#define PUSH _Pragma(\"pack(push)\")\nPUSH\nint x;\n";
2491 assert_eq!(clean(src), "#pragma pack(push) int x;");
2492 }
2493
2494 #[test]
2498 fn what_follows_a_pragma_operator_starts_a_line() {
2499 let mut run = Run::new();
2500 let out = run.raw("int x; _Pragma(\"pack(1)\") int y;\n");
2501 let starts: Vec<_> =
2502 out.iter().map(|tok| tok.flags.has(TokenFlags::START_OF_LINE)).collect();
2503 assert_eq!(
2506 starts,
2507 vec![true, false, false, true, false, false, false, false, false, true, false, false]
2508 );
2509 }
2510
2511 #[test]
2516 fn a_macro_that_came_to_nothing_hands_on_the_line_it_started() {
2517 let mut run = Run::new();
2518 let out = run.raw("#define E\nint x;\nE int y;\n");
2519 let starts: Vec<_> =
2520 out.iter().map(|tok| tok.flags.has(TokenFlags::START_OF_LINE)).collect();
2521 assert_eq!(starts, vec![true, false, false, true, false, false]);
2523 }
2524
2525 #[test]
2527 fn a_run_of_macros_that_came_to_nothing_hands_the_line_along() {
2528 let mut run = Run::new();
2529 let out = run.raw("#define E\n#define F(x)\nE F(1) E int y;\n");
2530 let starts: Vec<_> =
2531 out.iter().map(|tok| tok.flags.has(TokenFlags::START_OF_LINE)).collect();
2532 assert_eq!(starts, vec![true, false, false]);
2533 }
2534
2535 #[test]
2536 fn a_pragma_operator_that_is_not_given_a_string_is_reported() {
2537 let mut run = Run::new();
2538 run.go("_Pragma(x)\n");
2539 assert_eq!(run.messages(), vec!["`_Pragma` takes a single string literal".to_owned()]);
2540 }
2541
2542 #[test]
2543 fn an_include_reads_the_file_it_names() {
2544 let mut run = Run::new();
2545 run.file("/dir/one.h", "int from_the_header;\n");
2546 run.dir("/dir");
2547 assert_eq!(run.go("#include <one.h>\nint after;\n"), "int from_the_header; int after;");
2548 assert!(run.messages().is_empty());
2549 }
2550
2551 #[test]
2552 fn a_quoted_include_looks_next_to_the_including_file_first() {
2553 let mut run = Run::new();
2554 run.file("/local.h", "beside\n");
2555 run.file("/dir/local.h", "on the path\n");
2556 run.dir("/dir");
2557 assert_eq!(run.go("#include \"local.h\"\n"), "beside");
2558 assert!(run.messages().is_empty());
2559 }
2560
2561 #[test]
2562 fn an_angled_include_does_not_look_next_to_the_including_file() {
2563 let mut run = Run::new();
2564 run.file("/local.h", "beside\n");
2565 run.file("/dir/local.h", "on the path\n");
2566 run.dir("/dir");
2567 assert_eq!(run.go("#include <local.h>\n"), "on the path");
2568 }
2569
2570 #[test]
2571 fn a_macro_defined_in_a_header_is_visible_after_the_include() {
2572 let mut run = Run::new();
2573 run.file("/dir/defs.h", "#define N 42\n");
2574 run.dir("/dir");
2575 assert_eq!(run.go("#include <defs.h>\nint a = N;\n"), "int a = 42;");
2576 assert!(run.messages().is_empty());
2577 }
2578
2579 #[test]
2580 fn an_include_guard_keeps_the_second_read_empty() {
2581 let mut run = Run::new();
2582 run.file("/dir/g.h", "#ifndef G\n#define G\nonce\n#endif\n");
2583 run.dir("/dir");
2584 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
2585 assert!(run.messages().is_empty());
2586 assert_eq!(run.files(), 2, "the second include is not opened at all");
2587 }
2588
2589 fn named(name: &str, macros_only: bool) -> Preinclude {
2590 Preinclude { name: name.to_owned(), macros_only }
2591 }
2592
2593 #[test]
2594 fn a_command_line_include_contributes_its_text_and_an_imacros_contributes_none() {
2595 let mut run = Run::new();
2596 run.file("i.h", "from_include\n#define I 1\n");
2597 run.file("m.h", "from_macros\n#define M 1\n");
2598 assert_eq!(run.preinclude(&[named("i.h", false), named("m.h", true)]), "from_include");
2599 assert_eq!(run.go("I M\n"), "1 1");
2601 }
2602
2603 #[test]
2604 fn every_imacros_runs_before_every_include_whatever_order_the_command_line_was_in() {
2605 for files in
2608 [[named("i.h", false), named("m.h", true)], [named("m.h", true), named("i.h", false)]]
2609 {
2610 let mut run = Run::new();
2611 run.file("i.h", "#ifdef M\nsaw_it\n#else\nmissed_it\n#endif\n");
2612 run.file("m.h", "#define M 1\n");
2613 assert_eq!(run.preinclude(&files), "saw_it");
2614 }
2615 }
2616
2617 #[test]
2618 fn a_header_read_for_its_macros_is_not_read_again_by_an_include_that_its_guard_covers() {
2619 let mut run = Run::new();
2622 run.file("/dir/g.h", "#ifndef G\n#define G\ndeclarations\n#endif\n");
2623 run.dir("/dir");
2624 assert_eq!(run.preinclude(&[named("/dir/g.h", true)]), "");
2625 assert_eq!(run.go("#include <g.h>\n"), "");
2626 assert!(run.messages().is_empty());
2627 }
2628
2629 #[test]
2630 fn a_command_line_include_is_a_dependency_and_is_named_before_the_headers_it_reads() {
2631 let mut run = Run::new();
2632 run.file("i.h", "#include \"deep.h\"\n");
2633 run.file("deep.h", "\n");
2634 run.file("m.h", "\n");
2635 run.preinclude(&[named("i.h", false), named("m.h", true)]);
2636 let names: Vec<String> =
2637 run.pp.dependencies().iter().map(|d| d.path.to_string_lossy().into_owned()).collect();
2638 let names: Vec<String> = names.iter().map(|n| n.replace('\\', "/")).collect();
2639 assert_eq!(names, ["m.h", "i.h", "deep.h"]);
2640 }
2641
2642 #[test]
2643 fn a_prerequisite_is_spelled_without_the_dot_the_search_path_was_written_with() {
2644 let mut run = Run::new();
2648 run.file("d/f.h", "\n");
2649 run.dir("./d");
2650 run.go("#include <f.h>\n");
2651 let names: Vec<String> =
2652 run.pp.dependencies().iter().map(|d| d.path.to_string_lossy().into_owned()).collect();
2653 assert_eq!(names.iter().map(|n| n.replace('\\', "/")).collect::<Vec<_>>(), ["d/f.h"]);
2654 }
2655
2656 #[test]
2657 fn a_command_line_include_that_is_nowhere_is_reported_against_the_flag_that_named_it() {
2658 let mut run = Run::new();
2659 assert_eq!(run.preinclude(&[named("nope.h", false)]), "");
2660 assert_eq!(run.messages(), ["`nope.h` file not found"]);
2661 }
2662
2663 #[test]
2664 fn the_other_spelling_of_a_guard_is_recognised_too() {
2665 for guard in ["#if !defined(G)", "#if !defined G"] {
2666 let mut run = Run::new();
2667 run.file("/dir/g.h", &format!("{guard}\n#define G\nonce\n#endif\n"));
2668 run.dir("/dir");
2669 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
2670 assert_eq!(run.files(), 2, "{guard} should be a guard");
2671 }
2672 }
2673
2674 #[test]
2675 fn a_conditional_that_is_not_a_guard_does_not_skip_anything() {
2676 let mut run = Run::new();
2679 run.file("/dir/g.h", "#ifndef G\ntwice\n#endif\n");
2680 run.dir("/dir");
2681 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "twice twice");
2682 assert_eq!(run.files(), 3);
2683 }
2684
2685 #[test]
2686 fn a_token_outside_the_guard_stops_it_being_a_guard() {
2687 let mut run = Run::new();
2688 run.file("/dir/g.h", "#ifndef G\n#define G\n#endif\nalways\n");
2689 run.dir("/dir");
2690 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "always always");
2691 assert_eq!(run.files(), 3);
2692 }
2693
2694 #[test]
2695 fn pragma_once_skips_the_second_read_and_does_not_reach_the_output() {
2696 let mut run = Run::new();
2697 run.file("/dir/o.h", "#pragma once\nonce\n");
2698 run.dir("/dir");
2699 assert_eq!(run.go("#include <o.h>\n#include <o.h>\n"), "once");
2700 assert!(run.messages().is_empty());
2701 assert_eq!(run.files(), 2);
2702 }
2703
2704 #[test]
2705 fn pragma_once_in_the_main_file_is_a_warning_and_is_still_applied() {
2706 let mut run = Run::new();
2710 let src = "#pragma once\n#include <s.c>\nbody\n";
2711 run.file("/dir/s.c", src);
2712 run.dir("/dir");
2713 assert_eq!(run.go_named("/dir/s.c", src), "body");
2714 assert_eq!(run.severities(), vec![Severity::Warning]);
2715 assert_eq!(run.messages(), vec!["`#pragma once` in the main file".to_owned()]);
2716 assert_eq!(run.files(), 1);
2717 }
2718
2719 #[test]
2720 fn pragma_once_holds_across_two_spellings_of_the_one_path() {
2721 let mut run = Run::new();
2724 run.file("dir/s.c", "#pragma once\nbody\n");
2725 run.dir(".");
2726 assert_eq!(run.go("#include <dir/s.c>\n#include <dir/s.c>\n"), "body");
2727 assert!(run.messages().is_empty());
2728 assert_eq!(run.files(), 2);
2729 }
2730
2731 #[test]
2732 fn any_other_pragma_still_passes_through() {
2733 assert_eq!(clean("#pragma once_upon_a_time\n"), "#pragma once_upon_a_time");
2734 }
2735
2736 #[test]
2739 fn push_macro_and_pop_macro_put_a_definition_aside_and_bring_it_back() {
2740 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";
2741 assert_eq!(clean(src), "a 2 b 1");
2742 }
2743
2744 #[test]
2745 fn a_name_with_no_definition_pushes_and_pops_the_absence() {
2746 let src = "#pragma push_macro(\"X\")\n#define X 1\na X\n#pragma pop_macro(\"X\")\nb X\n";
2749 assert_eq!(clean(src), "a 1 b X");
2750 }
2751
2752 #[test]
2753 fn the_pushes_nest() {
2754 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";
2755 assert_eq!(clean(src), "a 3 b 2 c 1");
2756 }
2757
2758 #[test]
2759 fn a_pop_with_nothing_pushed_says_nothing() {
2760 assert_eq!(clean("#define X 1\n#pragma pop_macro(\"X\")\nX\n"), "1");
2763 assert_eq!(clean("#pragma pop_macro(\"Never\")\nx\n"), "x");
2764 }
2765
2766 #[test]
2767 fn the_pragma_operator_spelling_works_and_takes_effect_where_it_is_written() {
2768 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";
2773 assert_eq!(clean(src), "a 2 b 1");
2774 }
2775
2776 #[test]
2777 fn the_gcc_spelling_is_not_one_of_these_and_passes_through() {
2778 let src = "#define X 1\n#pragma GCC push_macro(\"X\")\n#undef X\n#define X 2\nX\n";
2782 assert_eq!(clean(src), "#pragma GCC push_macro(\"X\") 2");
2783 }
2784
2785 #[test]
2786 fn a_push_macro_that_is_not_the_shape_is_an_error() {
2787 for src in ["#pragma push_macro\n", "#pragma push_macro(X)\n", "#pragma pop_macro()\n"] {
2788 let mut run = Run::new();
2789 run.go(src);
2790 let word = if src.contains("push") { "push" } else { "pop" };
2791 assert_eq!(
2792 run.messages(),
2793 vec![format!("invalid `#pragma {word}_macro` directive")],
2794 "from {src:?}"
2795 );
2796 }
2797 }
2798
2799 #[test]
2800 fn a_string_that_does_not_spell_one_identifier_names_no_macro() {
2801 assert_eq!(clean("#pragma push_macro(\"a b\")\nx\n"), "x");
2804 assert_eq!(clean("#pragma push_macro(\"2\")\nx\n"), "x");
2805 }
2806
2807 #[test]
2808 fn what_follows_the_closing_parenthesis_is_the_usual_warning() {
2809 let mut run = Run::new();
2810 assert_eq!(run.go("#define X 1\n#pragma push_macro(\"X\") junk\nX\n"), "1");
2811 assert_eq!(run.severities(), vec![Severity::Warning]);
2812 assert_eq!(run.messages(), vec!["extra tokens after `#pragma`".to_owned()]);
2813 }
2814
2815 #[test]
2816 fn has_include_answers_from_the_search_path() {
2817 let mut run = Run::new();
2818 run.file("/dir/there.h", "");
2819 run.dir("/dir");
2820 let src = "#if __has_include(<there.h>)\nyes\n#endif\n\
2821 #if __has_include(<gone.h>)\nno\n#endif\n";
2822 assert_eq!(run.go(src), "yes");
2823 assert!(run.messages().is_empty(), "a header that is not there is an answer, not an error");
2824 }
2825
2826 #[test]
2827 fn has_include_asks_the_question_the_include_on_the_same_line_would() {
2828 let mut run = Run::new();
2832 run.file("/beside.h", "");
2833 let src = "#if __has_include(\"beside.h\")\nquoted\n#endif\n\
2834 #if __has_include(<beside.h>)\nangled\n#endif\n";
2835 assert_eq!(run.go(src), "quoted");
2836 }
2837
2838 #[test]
2839 fn has_include_next_starts_where_include_next_would() {
2840 let mut run = Run::new();
2841 run.file("/a/both.h", "#if __has_include_next(<both.h>)\nmore\n#endif\n");
2842 run.file("/b/both.h", "last\n");
2843 run.file("/a/only.h", "#if __has_include_next(<only.h>)\nmore\n#endif\n");
2844 run.dir("/a");
2845 run.dir("/b");
2846 assert_eq!(run.go("#include <both.h>\n"), "more");
2847 assert_eq!(run.go("#include <only.h>\n"), "", "there is nothing after /a to find it in");
2848 }
2849
2850 #[test]
2851 fn the_operand_of_has_include_is_not_macro_expanded() {
2852 let mut run = Run::new();
2855 run.file("/dir/linux/version.h", "");
2856 run.dir("/dir");
2857 let src = "#define linux 1\n#if __has_include(<linux/version.h>)\nyes\n#endif\n";
2858 assert_eq!(run.go(src), "yes");
2859 }
2860
2861 #[test]
2862 fn a_macro_may_expand_to_a_has_include() {
2863 let mut run = Run::new();
2865 run.file("/dir/there.h", "");
2866 run.dir("/dir");
2867 let src = "#define HAVE __has_include(<there.h>)\n#if HAVE\nyes\n#endif\n";
2868 assert_eq!(run.go(src), "yes");
2869 }
2870
2871 #[test]
2872 fn defined_says_the_has_operators_are_there() {
2873 let src = "#if defined(__has_include) && defined __has_builtin\nyes\n#endif\n";
2876 assert_eq!(clean(src), "yes");
2877 assert_eq!(clean("#ifdef __has_attribute\nyes\n#endif\n"), "yes");
2878 }
2879
2880 #[test]
2881 fn has_attribute_answers_out_of_the_matrix() {
2882 assert_eq!(clean("#if __has_attribute(packed)\nyes\n#endif\n"), "yes");
2886 assert_eq!(clean("#if __has_attribute(cold)\nyes\n#endif\n"), "");
2887 assert_eq!(clean("#if __has_attribute(no_such_attribute)\nyes\n#endif\n"), "");
2888 assert_eq!(clean("#if !__has_attribute(cold)\nno\n#endif\n"), "no");
2889 }
2890
2891 #[test]
2892 fn the_scoped_spelling_of_an_attribute_is_the_same_question() {
2893 assert_eq!(clean("#if __has_c_attribute(gnu::packed)\nyes\n#endif\n"), "");
2899 assert_eq!(clean("#if __has_c_attribute(deprecated)\nyes\n#endif\n"), "");
2900 }
2901
2902 #[test]
2903 fn has_builtin_answers_no_until_the_builtin_is_real() {
2904 assert_eq!(clean("#if __has_builtin(__builtin_expect)\nyes\n#endif\n"), "yes");
2905 assert_eq!(clean("#if __has_builtin(__builtin_clz)\nyes\n#endif\n"), "yes");
2906 assert_eq!(clean("#if __has_builtin(__builtin_alloca)\nyes\n#endif\n"), "yes");
2907 assert_eq!(clean("#if __has_builtin(__builtin_object_size)\nyes\n#endif\n"), "yes");
2908 assert_eq!(clean("#if __has_builtin(__atomic_signal_fence)\nyes\n#endif\n"), "");
2909 assert_eq!(clean("#if __has_builtin(__builtin_nonesuch)\nyes\n#endif\n"), "");
2910 }
2911
2912 #[test]
2913 fn has_feature_and_has_extension_read_the_same_table() {
2914 assert_eq!(clean("#if __has_feature(pragma_once)\nyes\n#endif\n"), "yes");
2917 assert_eq!(clean("#if __has_extension(pragma_once)\nyes\n#endif\n"), "yes");
2918 assert_eq!(clean("#if __has_extension(include_next)\nyes\n#endif\n"), "yes");
2919 assert_eq!(clean("#if __has_feature(include_next)\nyes\n#endif\n"), "");
2920 assert_eq!(clean("#if __has_feature(statement_expressions)\nyes\n#endif\n"), "");
2921 }
2922
2923 #[test]
2924 fn building_module_is_always_no_and_is_recognised_so_that_the_line_parses() {
2925 assert_eq!(clean("#if __building_module(m)\nyes\n#endif\n"), "");
2929 assert_eq!(clean("#if !__building_module(m)\nyes\n#endif\n"), "yes");
2930 assert_eq!(
2931 clean(
2932 "#if !defined(offsetof) || (__has_feature(modules) && !__building_module(x))\nyes\n#endif\n"
2933 ),
2934 "yes"
2935 );
2936 assert_eq!(clean("#ifdef __building_module\nyes\n#endif\n"), "yes");
2938 assert_eq!(clean("#if defined(__building_module)\nyes\n#endif\n"), "yes");
2939 }
2940
2941 #[test]
2942 fn a_has_operator_without_an_operand_is_reported() {
2943 let mut run = Run::new();
2944 run.go("#if __has_include\nyes\n#endif\n");
2945 assert_eq!(run.messages(), ["expected `(` after `__has_include`"]);
2946 let mut run = Run::new();
2947 run.go("#if __has_include(1)\nyes\n#endif\n");
2948 assert_eq!(run.messages(), ["expected a file name in `<>` or `\"\"`"]);
2949 let mut run = Run::new();
2950 run.go("#if __has_attribute(\"packed\")\nyes\n#endif\n");
2951 assert_eq!(run.messages(), ["expected an identifier as the operand of `__has_attribute`"]);
2952 }
2953
2954 #[test]
2955 fn the_has_operators_answer_in_ordinary_text_too() {
2956 assert_eq!(clean("f __has_feature(pragma_once)\n"), "f 1");
2960 assert_eq!(clean("b __has_builtin(__builtin_expect)\n"), "b 1");
2961 assert_eq!(clean("a __has_attribute(packed)\n"), "a 1");
2962 assert_eq!(clean("c __has_c_attribute(deprecated)\n"), "c 0");
2963 assert_eq!(clean("m __building_module(foo)\n"), "m 0");
2964 }
2965
2966 #[test]
2967 fn a_macro_that_expands_to_a_has_operator_is_answered_where_it_is_used() {
2968 assert_eq!(clean("#define HAVE __has_feature(pragma_once)\nx HAVE\n"), "x 1");
2971 assert_eq!(clean("#define HAVE(x) __has_attribute(x)\ny HAVE(packed)\n"), "y 1");
2972 }
2973
2974 #[test]
2975 fn a_has_operator_in_text_still_needs_its_operand() {
2976 let mut run = Run::new();
2977 run.go("tail __has_attribute;\n");
2978 assert_eq!(run.messages(), ["expected `(` after `__has_attribute`"]);
2979 }
2980
2981 #[test]
2982 fn the_header_operators_are_refused_in_ordinary_text() {
2983 let mut run = Run::new();
2986 run.file("/dir/there.h", "");
2987 run.dir("/dir");
2988 run.go("a __has_include(<there.h>)\n");
2989 assert_eq!(run.messages(), ["`__has_include` used outside of a preprocessing directive"]);
2990 let mut run = Run::new();
2991 run.go("b __has_include_next(\"x.h\")\n");
2992 assert_eq!(
2993 run.messages(),
2994 ["`__has_include_next` used outside of a preprocessing directive"]
2995 );
2996 }
2997
2998 #[test]
2999 fn the_predefined_set_is_visible_to_the_source_file() {
3000 let mut run = Run::new();
3001 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3002 let src = "#if defined(__x86_64__) && defined(__linux__) && __SIZEOF_LONG__ == 8\n\
3003 yes\n#endif\n";
3004 assert_eq!(run.go(src), "yes");
3005 assert!(run.messages().is_empty());
3006 }
3007
3008 #[test]
3009 fn the_predefined_set_follows_the_target_and_not_the_host() {
3010 let mut run = Run::new();
3011 run.predefine("aarch64-unknown-linux-gnu", &Predef::new());
3012 assert_eq!(
3013 run.go("#ifdef __x86_64__\nno\n#endif\n#ifdef __aarch64__\nyes\n#endif\n"),
3014 "yes"
3015 );
3016 }
3017
3018 #[test]
3019 fn a_predefined_macro_expands_where_it_is_used() {
3020 let mut run = Run::new();
3021 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3022 assert_eq!(run.go("__SIZE_TYPE__ n;\n"), "long unsigned int n;");
3023 }
3024
3025 #[test]
3026 fn a_command_line_define_is_a_definition_like_any_other() {
3027 let mut opts = Predef::new();
3028 opts.defines = vec!["FOO".to_owned(), "BAR=3".to_owned()];
3029 opts.undefines = vec!["__linux__".to_owned()];
3030 let mut run = Run::new();
3031 run.predefine("x86_64-unknown-linux-gnu", &opts);
3032 let src = "#if FOO && BAR == 3 && !defined(__linux__)\nyes\n#endif\n";
3033 assert_eq!(run.go(src), "yes");
3034 assert!(run.messages().is_empty());
3035 }
3036
3037 #[test]
3038 fn the_predefined_set_produces_no_tokens_of_its_own() {
3039 let mut run = Run::new();
3042 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3043 assert_eq!(run.go("alone\n"), "alone");
3044 }
3045
3046 #[test]
3047 fn the_predefined_files_are_named_the_way_gcc_names_them() {
3048 let mut run = Run::new();
3049 let mut opts = Predef::new();
3050 opts.defines = vec!["FOO=1".to_owned()];
3051 run.predefine("x86_64-unknown-linux-gnu", &opts);
3052 let names: Vec<&str> = run.sources.files().iter().map(|f| f.name.as_str()).collect();
3053 assert_eq!(names, ["<built-in>", "<command-line>"]);
3054 }
3055
3056 #[test]
3057 fn a_dialect_without_the_gnu_extensions_says_so() {
3058 let mut opts = Predef::new();
3059 opts.gnu_extensions = false;
3060 opts.std = Std::C99;
3061 let mut run = Run::new();
3062 run.predefine("x86_64-unknown-linux-gnu", &opts);
3063 let src = "#if defined(__STRICT_ANSI__) && __STDC_VERSION__ == 199901L && !defined(linux)\n\
3064 yes\n#endif\n";
3065 assert_eq!(run.go(src), "yes");
3066 }
3067
3068 #[test]
3069 fn the_date_and_time_are_the_same_for_the_whole_translation_unit() {
3070 let mut opts = Predef::new();
3071 opts.timestamp = Timestamp::from_unix(0);
3072 let mut run = Run::new();
3073 run.predefine("x86_64-unknown-linux-gnu", &opts);
3074 assert_eq!(run.go("__DATE__ __TIME__\n"), "\"Jan 1 1970\" \"00:00:00\"");
3075 }
3076
3077 #[test]
3078 fn a_has_operator_in_a_dead_branch_is_not_asked_about() {
3079 assert_eq!(clean("#if 0\n#if __has_include\n#endif\n#endif\nafter\n"), "after");
3081 }
3082
3083 #[test]
3084 fn a_conditional_may_not_span_an_include() {
3085 let mut run = Run::new();
3089 run.file("/dir/open.h", "#if 1\n");
3090 run.dir("/dir");
3091 run.go("#include <open.h>\nkept\n#endif\n");
3092 let messages = run.messages();
3093 assert_eq!(messages.len(), 2);
3094 assert!(messages[0].contains("unterminated"));
3095 assert!(messages[1].contains("without"));
3096 }
3097
3098 #[test]
3099 fn include_next_continues_after_the_directory_the_file_came_from() {
3100 let mut run = Run::new();
3103 run.file("/a/limits.h", "wrapper\n#include_next <limits.h>\n");
3104 run.file("/b/limits.h", "real\n");
3105 run.dir("/a");
3106 run.dir("/b");
3107 assert_eq!(run.go("#include <limits.h>\n"), "wrapper real");
3108 assert!(run.messages().is_empty());
3109 }
3110
3111 #[test]
3112 fn a_computed_include_is_expanded_first() {
3113 let mut run = Run::new();
3114 run.file("/dir/sub/thing.h", "computed\n");
3115 run.dir("/dir");
3116 let src = "#define HEADER <sub/thing.h>\n#include HEADER\n";
3117 assert_eq!(run.go(src), "computed");
3118 assert!(run.messages().is_empty());
3119 let mut run = Run::new();
3121 run.file("/dir/sub/thing.h", "computed\n");
3122 run.dir("/dir");
3123 assert_eq!(run.go("#define H \"sub/thing.h\"\n#include H\n"), "computed");
3124 }
3125
3126 #[test]
3127 fn a_header_that_is_not_there_says_where_it_looked() {
3128 let mut run = Run::new();
3129 run.dir("/dir");
3130 run.go("#include <nope.h>\n");
3131 let diagnostics = run.pp.take_diagnostics();
3132 assert_eq!(diagnostics.len(), 1);
3133 assert_eq!(diagnostics[0].code, Some("E0341"));
3134 assert_eq!(diagnostics[0].message, "`nope.h` file not found");
3135 assert!(diagnostics[0].children[0].message.contains("/dir"));
3136 }
3137
3138 #[test]
3145 fn a_header_that_is_not_there_says_why_the_system_directories_are_missing() {
3146 let mut run = Run::new();
3147 run.search.explain_missing_system("aarch64-macos needs a macOS SDK and there is none here");
3148 run.go("#include <stdio.h>\n");
3149 let diagnostics = run.pp.take_diagnostics();
3150 assert_eq!(diagnostics.len(), 1);
3151 assert_eq!(diagnostics[0].message, "`stdio.h` file not found");
3152 assert!(diagnostics[0].children[0].message.contains("search path is empty"));
3153 assert!(diagnostics[0].children[1].message.contains("needs a macOS SDK"));
3154 let mut run = Run::new();
3156 run.dir("/dir");
3157 run.go("#include <nope.h>\n");
3158 assert_eq!(run.pp.take_diagnostics()[0].children.len(), 1);
3159 }
3160
3161 #[test]
3162 fn an_include_that_is_not_a_header_name_is_reported() {
3163 let mut run = Run::new();
3164 run.go("#include 3\n");
3165 let diagnostics = run.pp.take_diagnostics();
3166 assert_eq!(diagnostics[0].code, Some("E0343"));
3167 }
3168
3169 #[test]
3170 fn a_header_that_includes_itself_stops() {
3171 let mut run = Run::new();
3172 run.file("/dir/loop.h", "#include <loop.h>\n");
3173 run.dir("/dir");
3174 run.go("#include <loop.h>\n");
3175 let diagnostics = run.pp.take_diagnostics();
3176 assert_eq!(diagnostics.len(), 1, "one complaint, not one per level");
3177 assert_eq!(diagnostics[0].code, Some("E0342"));
3178 }
3179
3180 #[test]
3181 fn an_include_in_a_dead_branch_is_not_read() {
3182 let mut run = Run::new();
3183 assert_eq!(run.go("#if 0\n#include <nothing.h>\n#endif\nafter\n"), "after");
3184 assert!(run.messages().is_empty(), "a skipped include is not resolved");
3185 }
3186
3187 #[test]
3188 fn embed_writes_the_bytes_of_the_resource() {
3189 let mut run = Run::new();
3190 run.bytes("/logo.bin", &[0, 1, 127, 128, 255]);
3191 assert_eq!(run.go("#embed \"logo.bin\"\n"), "0, 1, 127, 128, 255");
3192 assert!(run.messages().is_empty());
3193 }
3194
3195 #[test]
3196 fn an_embed_is_a_valid_initializer_on_both_sides_of_empty() {
3197 let mut run = Run::new();
3202 run.bytes("/some.bin", &[7, 8]);
3203 run.bytes("/none.bin", &[]);
3204 let line = |name: &str| {
3205 format!("{{\n#embed \"{name}\" prefix(0xEF,) suffix(,0xFE) if_empty(0)\n}}\n")
3206 };
3207 assert_eq!(run.go(&line("some.bin")), "{ 0xEF,7, 8 ,0xFE }");
3208 assert_eq!(run.go_named("/other.c", &line("none.bin")), "{ 0 }");
3209 assert!(run.messages().is_empty());
3210 }
3211
3212 #[test]
3213 fn the_limit_and_the_offset_choose_a_window_of_the_resource() {
3214 let mut run = Run::new();
3215 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
3216 assert_eq!(run.go("#embed \"eight.bin\" limit(3)\n"), "1, 2, 3");
3217 assert_eq!(
3218 run.go_named("/b.c", "#embed \"eight.bin\" gnu::offset(4) limit(3)\n"),
3219 "5, 6, 7"
3220 );
3221 assert_eq!(run.go_named("/c.c", "#embed \"eight.bin\" limit(0) if_empty(9)\n"), "9");
3224 assert_eq!(run.go_named("/d.c", "#embed \"eight.bin\" gnu::offset(99)\n"), "");
3225 assert!(run.messages().is_empty());
3226 }
3227
3228 #[test]
3229 fn the_limit_is_a_constant_expression_and_not_just_a_number() {
3230 let mut run = Run::new();
3233 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
3234 assert_eq!(
3235 run.go("#define CHUNK 2\n#embed \"eight.bin\" limit(CHUNK * 2)\n"),
3236 "1, 2, 3, 4"
3237 );
3238 assert!(run.messages().is_empty());
3239 }
3240
3241 #[test]
3242 fn a_misspelled_embed_parameter_is_refused_rather_than_ignored() {
3243 let mut run = Run::new();
3246 run.bytes("/eight.bin", &[1, 2]);
3247 assert_eq!(run.go("#embed \"eight.bin\" limits(1)\n"), "");
3248 assert_eq!(run.messages(), vec!["unknown `#embed` parameter `limits`".to_owned()]);
3249 let mut vendor = Run::new();
3250 vendor.bytes("/eight.bin", &[1, 2]);
3251 assert_eq!(vendor.go("#embed \"eight.bin\" clang::offset(1)\n"), "");
3252 assert_eq!(
3253 vendor.messages(),
3254 vec!["unknown `#embed` parameter `clang::offset`".to_owned()]
3255 );
3256 }
3257
3258 #[test]
3259 fn a_missing_embed_resource_is_reported_as_a_resource() {
3260 let mut run = Run::new();
3261 run.go("#embed <nothing.bin>\n");
3262 assert_eq!(run.messages(), vec!["`nothing.bin` resource not found".to_owned()]);
3263 }
3264
3265 #[test]
3266 fn has_embed_tells_missing_from_present_from_empty() {
3267 let mut run = Run::new();
3271 run.bytes("/some.bin", &[1]);
3272 run.bytes("/none.bin", &[]);
3273 let src = "#if __has_embed(\"none.bin\") == __STDC_EMBED_EMPTY__\nempty\n#endif\n\
3274 #if __has_embed(\"some.bin\") == __STDC_EMBED_FOUND__\nfound\n#endif\n\
3275 #if __has_embed(\"gone.bin\") == __STDC_EMBED_NOT_FOUND__\ngone\n#endif\n";
3276 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
3277 assert_eq!(run.go(src), "empty found gone");
3278 assert!(run.messages().is_empty());
3279 }
3280
3281 #[test]
3282 fn has_embed_takes_the_limit_into_account() {
3283 let mut run = Run::new();
3286 run.bytes("/some.bin", &[1, 2, 3]);
3287 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
3288 let src = "#if __has_embed(\"some.bin\" limit(0)) == __STDC_EMBED_EMPTY__\nempty\n#endif\n";
3289 assert_eq!(run.go(src), "empty");
3290 assert!(run.messages().is_empty());
3291 }
3292
3293 #[test]
3294 fn a_directive_may_have_space_before_the_hash_and_after_it() {
3295 assert_eq!(clean(" # define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
3296 }
3297
3298 #[test]
3299 fn a_definition_survives_across_a_conditional() {
3300 assert_eq!(clean("#if 1\n#define F 7\n#endif\nF\n"), "7");
3301 }
3302
3303 #[test]
3304 fn an_empty_if_expression_is_reported() {
3305 let mut run = Run::new();
3306 run.go("#if\n#endif\n");
3307 assert_eq!(run.messages(), vec!["`#if` with no expression".to_owned()]);
3308 }
3309
3310 #[test]
3311 fn the_file_and_the_line_say_where_the_use_is() {
3312 let mut run = Run::new();
3313 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3314 assert_eq!(run.go("__FILE__ __LINE__\n__LINE__\n"), "\"/main.c\" 1 2");
3315 assert!(run.messages().is_empty());
3316 }
3317
3318 #[test]
3319 fn a_macro_that_mentions_the_line_answers_with_the_call() {
3320 let mut run = Run::new();
3321 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3322 run.file("/where.h", "#define WHERE __FILE__ __LINE__\n");
3323 assert_eq!(run.go("#include \"where.h\"\n\n\nWHERE\n"), "\"/main.c\" 4");
3327 assert!(run.messages().is_empty());
3328 }
3329
3330 #[test]
3331 fn the_file_name_is_the_file_without_the_directories() {
3332 let mut run = Run::new();
3333 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3334 assert_eq!(run.go_named("/deep/down/main.c", "__FILE_NAME__\n"), "\"main.c\"");
3335 }
3336
3337 #[test]
3338 fn a_backslash_in_the_name_is_escaped() {
3339 let mut run = Run::new();
3340 run.predefine("x86_64-pc-windows-msvc", &Predef::new());
3341 let text = run.go_named("C:\\src\\main.c", "__FILE__ __FILE_NAME__\n");
3344 assert_eq!(text, "\"C:\\\\src\\\\main.c\" \"main.c\"");
3345 }
3346
3347 #[test]
3348 fn the_base_file_is_the_one_named_on_the_command_line() {
3349 let mut run = Run::new();
3350 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3351 run.file("/deep.h", "__FILE__ __BASE_FILE__\n");
3352 assert_eq!(run.go("#include \"deep.h\"\n"), "\"/deep.h\" \"/main.c\"");
3353 assert!(run.messages().is_empty());
3354 }
3355
3356 #[test]
3357 fn a_prefix_map_rewrites_the_file_and_the_base_file_and_not_the_file_name() {
3358 let mut run = Run::mapping(&[("/build", ".")]);
3359 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3360 run.file("/build/deep.h", "__FILE__ __BASE_FILE__ __FILE_NAME__\n");
3361 let text = run.go_named("/build/main.c", "#include \"deep.h\"\n");
3367 assert_eq!(slashes(&text), "\"./deep.h\" \"./main.c\" \"deep.h\"");
3370 assert!(run.messages().is_empty());
3371 }
3372
3373 #[test]
3374 fn the_last_rewrite_that_matches_is_the_one_that_acts() {
3375 let mut run = Run::mapping(&[("/build", "src"), ("/build/gen", "generated")]);
3378 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3379 assert_eq!(run.go_named("/build/gen/made.c", "__FILE__\n"), "\"generated/made.c\"");
3380
3381 let mut run = Run::mapping(&[("/build", "src"), ("/build/gen", "generated")]);
3382 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3383 assert_eq!(run.go_named("/build/hand.c", "__FILE__\n"), "\"src/hand.c\"");
3384
3385 let mut run = Run::mapping(&[("/build", "src")]);
3388 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3389 assert_eq!(run.go_named("/elsewhere/main.c", "__FILE__\n"), "\"/elsewhere/main.c\"");
3390 }
3391
3392 #[test]
3393 fn a_rewrite_matches_the_characters_and_not_the_directories() {
3394 let mut run = Run::mapping(&[("/bui", "X")]);
3400 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3401 assert_eq!(run.go_named("/build/main.c", "__FILE__\n"), "\"Xld/main.c\"");
3402 }
3403
3404 #[test]
3405 fn the_include_level_counts_the_headers_above_it() {
3406 let mut run = Run::new();
3407 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3408 run.file("/one.h", "__INCLUDE_LEVEL__\n#include \"two.h\"\n");
3409 run.file("/two.h", "__INCLUDE_LEVEL__\n");
3410 assert_eq!(run.go("__INCLUDE_LEVEL__\n#include \"one.h\"\n"), "0 1 2");
3411 assert!(run.messages().is_empty());
3412 }
3413
3414 #[test]
3415 fn the_counter_is_a_different_number_every_time() {
3416 let mut run = Run::new();
3417 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3418 assert_eq!(run.go("__COUNTER__ __COUNTER__ __COUNTER__\n"), "0 1 2");
3419 }
3420
3421 #[test]
3422 fn the_counter_advances_once_per_argument_rather_than_once_per_use() {
3423 let mut run = Run::new();
3424 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3425 assert_eq!(run.go("#define TWICE(x) x x\nTWICE(__COUNTER__) __COUNTER__\n"), "0 0 1");
3429 }
3430
3431 #[test]
3432 fn the_line_is_a_number_an_if_can_use() {
3433 let mut run = Run::new();
3434 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3435 assert_eq!(run.go("#if __LINE__ == 1 && __INCLUDE_LEVEL__ == 0\nyes\n#endif\n"), "yes");
3436 assert!(run.messages().is_empty());
3437 }
3438
3439 #[test]
3440 fn the_dynamic_macros_are_defined_like_any_others() {
3441 let mut run = Run::new();
3442 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3443 let src = "#ifdef __FILE__\nyes\n#endif\n#undef __LINE__\n#ifndef __LINE__\ngone\n#endif\n";
3444 assert_eq!(run.go(src), "yes gone");
3445 assert!(run.messages().is_empty(), "`#undef` of a builtin is allowed, as it is in GCC");
3446 }
3447
3448 #[test]
3449 fn redefining_a_dynamic_macro_warns_and_points_at_the_built_in_file() {
3450 let mut run = Run::new();
3451 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3452 assert_eq!(run.go("#define __FILE__ \"mine.c\"\n__FILE__\n"), "\"mine.c\"");
3453 let complaints = run.pp.take_diagnostics();
3454 assert_eq!(complaints.len(), 1);
3455 assert_eq!(complaints[0].code, Some("W0301"));
3456 let previous = complaints[0].children.first().expect("a note saying where it was");
3457 assert_eq!(run.sources.lookup(previous.span.lo).map(|loc| loc.file), {
3458 let built_in = run.sources.files().iter().find(|f| f.name == BUILT_IN);
3459 built_in.map(|f| f.id)
3460 });
3461 }
3462
3463 #[test]
3464 fn destringizing_undoes_what_stringizing_did() {
3465 assert_eq!(destringize(r#""a \"b\" c""#), r#"a "b" c"#);
3466 assert_eq!(destringize(r#""a \\ b""#), r"a \ b");
3467 assert_eq!(destringize(r#"L"wide""#), "wide");
3468 }
3469}