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 let alternative = is_alternative(body.first().and_then(ident_of), names);
387 self.directive(&body, first.span, out, cx, names);
388 scan = match scan {
389 Scan::Start => match opens {
393 Some(name) if self.conds.len() == depth_on_entry + 1 => Scan::Inside(name),
394 _ => Scan::No,
395 },
396 Scan::Inside(name) if self.conds.len() == depth_on_entry => Scan::Closed(name),
397 Scan::Inside(_) if alternative && self.conds.len() == depth_on_entry + 1 => {
403 Scan::No
404 }
405 Scan::Inside(name) => Scan::Inside(name),
406 Scan::Closed(_) | Scan::No => Scan::No,
407 };
408 } else {
409 body.clear();
410 reader.line(cx.interner, &mut body);
411 if self.live() {
412 let operator = ident_of(&first) == Some(names.pragma_op)
418 || body.iter().any(|t| ident_of(t) == Some(names.pragma_op));
419 if operator {
420 self.flush(&mut text, out, cx, names);
421 }
422 text.push(Tok::new(first));
423 text.extend(body.iter().copied().map(Tok::new));
424 if operator {
425 self.flush(&mut text, out, cx, names);
426 }
427 }
428 if !matches!(scan, Scan::Inside(_)) {
430 scan = Scan::No;
431 }
432 }
433 let complaints = reader.take_diagnostics();
436 if was_live || self.live() {
437 self.diagnostics.extend(complaints);
438 }
439 }
440 self.flush(&mut text, out, cx, names);
441 self.diagnostics.extend(reader.take_diagnostics());
442
443 if let Scan::Closed(name) = scan {
446 if self.macros.is_defined(name) {
447 if let Some(frame) = self.stack.last() {
448 self.seen.entry(frame.id.clone()).or_insert(Guard::Macro(name));
449 }
450 }
451 }
452
453 for cond in self.conds.drain(depth_on_entry..) {
456 self.diagnostics
457 .push(Diagnostic::error("unterminated `#if`", cond.span).with_code("E0330"));
458 }
459 }
460
461 fn live(&self) -> bool {
463 self.conds.last().is_none_or(|c| c.live)
464 }
465
466 fn flush(
468 &mut self,
469 text: &mut Vec<Tok>,
470 out: &mut Vec<Tok>,
471 cx: &mut Context<'_>,
472 names: &Names,
473 ) {
474 if text.is_empty() {
475 return;
476 }
477 let taken = std::mem::take(text);
478 let expanded = self.expander.expand_toks(taken, &self.macros, cx.interner, cx.sources);
479 self.diagnostics.append(&mut self.expander.take_diagnostics());
480 let expanded = self.resolve_has(expanded, cx, names, Pass::Text);
485 self.pragma_operator(expanded, out, cx.interner, names);
486 }
487
488 fn directive(
490 &mut self,
491 body: &[PpToken],
492 hash: Span,
493 out: &mut Vec<Tok>,
494 cx: &mut Context<'_>,
495 names: &Names,
496 ) {
497 let Some(first) = body.first().copied() else {
498 return;
499 };
500 let name = ident_of(&first);
501 let rest = &body[1..];
502
503 if name == Some(names.r#if) {
506 let value = self.live() && self.eval(rest, hash, cx, names);
507 self.open(hash, value);
508 return;
509 }
510 if name == Some(names.ifdef) || name == Some(names.ifndef) {
511 let want = name == Some(names.ifdef);
512 let value = self.live() && self.defined_check(rest, hash, want, names);
513 self.open(hash, value);
514 return;
515 }
516 if name == Some(names.elif) || name == Some(names.elifdef) || name == Some(names.elifndef) {
517 self.elif(name, rest, hash, cx, names);
518 return;
519 }
520 if name == Some(names.r#else) {
521 self.branch_else(rest, hash);
522 return;
523 }
524 if name == Some(names.endif) {
525 self.endif(rest, hash);
526 return;
527 }
528 if !self.live() {
529 return;
533 }
534
535 if name.is_none() && decimal(&first, cx.interner).is_some() {
539 self.line_marker(body, hash, out.len(), cx);
540 return;
541 }
542
543 let interner = &mut *cx.interner;
544 if name == Some(names.define) {
545 let (def, diagnostics) = parse_define(rest, interner);
546 self.diagnostics.extend(diagnostics);
547 if let Some(def) = def {
548 if let Some(problem) = self.macros.define(def, interner) {
549 self.diagnostics.push(problem);
550 }
551 }
552 } else if name == Some(names.undef) {
553 self.undef(rest, hash, interner);
554 } else if name == Some(names.error) || name == Some(names.warning) {
555 self.message(rest, hash, name == Some(names.error), interner);
556 } else if name == Some(names.line) {
557 self.line(rest, hash, out.len(), cx);
558 } else if name == Some(names.pragma) {
559 if rest.len() == 1 && ident_of(&rest[0]) == Some(names.once) {
566 self.pragma_once(rest[0].span);
567 } else if !self.macro_stack_pragma(rest, hash, interner, names) {
568 self.pass_through(body, hash, out);
569 }
570 } else if name == Some(names.include) || name == Some(names.include_next) {
571 self.include(rest, hash, name == Some(names.include_next), out, cx, names);
572 } else if name == Some(names.embed) {
573 self.embed(rest, hash, out, cx);
574 } else {
575 self.diagnostics.push(
576 Diagnostic::error("invalid preprocessing directive", first.span).with_code("E0332"),
577 );
578 }
579 }
580
581 fn macro_stack_pragma(
593 &mut self,
594 rest: &[PpToken],
595 at: Span,
596 interner: &mut Interner,
597 names: &Names,
598 ) -> bool {
599 let which = match rest.first().and_then(ident_of) {
600 Some(name) if name == names.push_macro => names.push_macro,
601 Some(name) if name == names.pop_macro => names.pop_macro,
602 _ => return false,
603 };
604 let word = if which == names.push_macro { "push_macro" } else { "pop_macro" };
605 let [_, open, text, close, extra @ ..] = rest else {
610 self.invalid_pragma(word, at);
611 return true;
612 };
613 if open.punct() != Some(Punct::LParen)
614 || text.kind != PpTokenKind::StringLit
615 || close.punct() != Some(Punct::RParen)
616 {
617 self.invalid_pragma(word, at);
618 return true;
619 }
620 self.extra_tokens(extra, "#pragma");
621 let Some(name) = identifier_in(*text, interner) else {
626 return true;
627 };
628 if which == names.push_macro {
629 self.macros.push_macro(name);
630 } else {
631 self.macros.pop_macro(name);
632 }
633 true
634 }
635
636 fn invalid_pragma(&mut self, word: &str, at: Span) {
637 self.diagnostics.push(
638 Diagnostic::error(format!("invalid `#pragma {word}` directive"), at).with_code("E0672"),
639 );
640 }
641
642 fn pragma_once(&mut self, at: Span) {
644 if self.stack.len() <= 1 {
649 self.diagnostics.push(
650 Diagnostic::warning("`#pragma once` in the main file", at).with_code("W0332"),
651 );
652 }
653 if let Some(frame) = self.stack.last() {
654 self.seen.insert(frame.id.clone(), Guard::Once);
655 }
656 }
657
658 fn skip(&self, id: &Path) -> bool {
660 match self.seen.get(id) {
661 Some(Guard::Once) => true,
662 Some(Guard::Macro(name)) => self.macros.is_defined(*name),
663 None => false,
664 }
665 }
666
667 fn pass_through(&mut self, body: &[PpToken], hash: Span, out: &mut Vec<Tok>) {
669 let _ = self;
670 out.push(Tok::synthetic(
671 PpTokenKind::Punct(Punct::Hash),
672 None,
673 TokenFlags::START_OF_LINE,
674 hash,
675 ));
676 for (at, token) in body.iter().copied().enumerate() {
681 let mut token = Tok::new(token);
682 if at == 0 {
683 token.flags = token.flags.without(TokenFlags::LEADING_SPACE);
684 }
685 out.push(token);
686 }
687 }
688
689 fn include(
691 &mut self,
692 rest: &[PpToken],
693 hash: Span,
694 is_next: bool,
695 out: &mut Vec<Tok>,
696 cx: &mut Context<'_>,
697 names: &Names,
698 ) {
699 let Some(header) = self.header_of(rest, hash, cx) else {
700 return;
701 };
702 let (form, relative_to, from) = self.where_to_look(&header, is_next, cx);
703 let found = cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from);
704 let Some(found) = found else {
705 let tried = cx.search.tried(&header.name, form, relative_to.as_deref(), from);
706 self.not_found(&header.name, hash, &tried, cx.search.missing_system());
707 return;
708 };
709 self.read(found, hash, out, cx, names);
710 }
711
712 fn not_found(&mut self, name: &str, at: Span, tried: &[PathBuf], why: Option<&str>) {
719 let where_looked = if tried.is_empty() && Path::new(name).is_absolute() {
723 "the name is an absolute path, so the search path was not used".to_owned()
724 } else if tried.is_empty() {
725 "the include search path is empty".to_owned()
726 } else {
727 let list: Vec<String> =
728 tried.iter().map(|d| d.to_string_lossy().into_owned()).collect();
729 format!("searched: {}", list.join(", "))
730 };
731 let mut said = Diagnostic::error(format!("`{name}` file not found"), at)
732 .with_code("E0341")
733 .note(where_looked, at);
734 if let Some(why) = why {
735 said = said.note(why, at);
736 }
737 self.diagnostics.push(said);
738 }
739
740 fn read(
747 &mut self,
748 found: Found,
749 at: Span,
750 out: &mut Vec<Tok>,
751 cx: &mut Context<'_>,
752 names: &Names,
753 ) {
754 let id = cx.fs.identity(&found.path);
755 if self.dep_ids.insert(id.clone()) {
760 let path = rucc_session::path_key(&found.path);
767 self.deps.push(Dependency { path, is_system: found.is_system });
768 }
769 if self.skip(&id) {
774 return;
775 }
776 if self.stack.len() >= cx.max_include_depth as usize {
777 let mut diagnostic = Diagnostic::error("`#include` nested too deeply", at)
778 .with_code("E0342")
779 .note("a header that includes itself with no include guard is the usual cause", at);
780 if let Some(outer) = self.stack.first().filter(|f| !f.at.is_dummy()) {
781 diagnostic = diagnostic.note("the outermost include is here", outer.at);
782 }
783 self.diagnostics.push(diagnostic);
784 return;
785 }
786 let added = cx.sources.add_shared(found.name.clone(), found.bytes.clone(), Some(at));
787 let file = match added {
788 Ok(file) => file,
789 Err(full) => {
790 self.diagnostics.push(Diagnostic::error(full.to_string(), at).with_code("E0344"));
791 return;
792 }
793 };
794 if found.is_system || cx.sources.is_system(at.lo) {
802 cx.sources.mark_system(file);
803 }
804 self.stack.push(Frame {
805 at,
806 dir: found.path.parent().map(Path::to_path_buf),
807 id,
808 path: found.path,
809 next: found.next,
810 });
811 self.process(file, out, cx, names);
812 self.stack.pop();
813 }
814
815 fn embed(&mut self, rest: &[PpToken], hash: Span, out: &mut Vec<Tok>, cx: &mut Context<'_>) {
817 let Some((header, params)) = self.embed_line(rest, hash, cx) else {
818 return;
819 };
820 let Some(found) = self.find(&header, false, cx) else {
821 self.diagnostics.push(
822 Diagnostic::error(format!("`{}` resource not found", header.name), hash)
823 .with_code("E0341")
824 .note("an `#embed` resource is looked for on the include path", hash),
825 );
826 return;
827 };
828 embed::tokens(found.bytes.as_slice(), ¶ms, hash, cx.interner, out);
833 }
834
835 fn embed_line(
837 &mut self,
838 rest: &[PpToken],
839 hash: Span,
840 cx: &mut Context<'_>,
841 ) -> Option<(Header, embed::Params)> {
842 if rest.is_empty() {
843 self.bad_header(hash);
844 return None;
845 }
846 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
847 let line = if line[0].kind == PpTokenKind::HeaderName {
853 line
854 } else {
855 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
856 self.diagnostics.append(&mut self.expander.take_diagnostics());
857 expanded
858 };
859 let Some(used) = embed::header_length(&line) else {
860 self.bad_header(line.first().map_or(hash, |t| t.report_span()));
861 return None;
862 };
863 let header = if line[0].kind == PpTokenKind::HeaderName {
864 header_from_token(spelling(line[0], cx.interner))
865 } else {
866 let spellings: Vec<&str> =
867 line[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
868 header_from_tokens(&spellings)
869 };
870 let Some(header) = header else {
871 self.bad_header(line[0].report_span());
872 return None;
873 };
874 let params = self.embed_params(&line[used..], hash, cx)?;
875 Some((header, params))
876 }
877
878 fn embed_params(
880 &mut self,
881 line: &[Tok],
882 at: Span,
883 cx: &mut Context<'_>,
884 ) -> Option<embed::Params> {
885 let Preprocessor { expander, macros, diagnostics, .. } = self;
886 let sources = &mut *cx.sources;
887 let mut expand = |toks: Vec<Tok>, interner: &mut Interner| {
888 expander.expand_toks(toks, macros, interner, sources)
889 };
890 let params = embed::parse(line, at, cx.interner, diagnostics, &mut expand);
891 self.diagnostics.append(&mut self.expander.take_diagnostics());
892 params
893 }
894
895 fn where_to_look(
906 &self,
907 header: &Header,
908 is_next: bool,
909 cx: &Context<'_>,
910 ) -> (IncludeForm, Option<PathBuf>, usize) {
911 let form = if header.angled { IncludeForm::Angled } else { IncludeForm::Quoted };
912 let frame = self.stack.last();
913 let from = if is_next {
914 frame.map_or(0, |f| f.next).max(cx.search.start(form))
915 } else {
916 cx.search.start(form)
917 };
918 let relative_to = if is_next { None } else { frame.and_then(|f| f.dir.clone()) };
919 (form, relative_to, from)
920 }
921
922 fn find(&self, header: &Header, is_next: bool, cx: &Context<'_>) -> Option<Found> {
924 let (form, relative_to, from) = self.where_to_look(header, is_next, cx);
925 cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from)
926 }
927
928 fn header_of(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>) -> Option<Header> {
930 if let Some(first) = rest.first().copied() {
931 if first.kind == PpTokenKind::HeaderName {
932 let text = first.value.map_or("", |v| cx.interner.resolve(v));
933 let header = header_from_token(text);
934 if header.is_none() {
935 self.bad_header(first.span);
936 }
937 self.extra_tokens(&rest[1..], "#include");
938 return header;
939 }
940 }
941 if rest.is_empty() {
945 self.bad_header(hash);
946 return None;
947 }
948 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
949 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
950 self.diagnostics.append(&mut self.expander.take_diagnostics());
951 let spellings: Vec<&str> = expanded.iter().map(|t| spelling(*t, cx.interner)).collect();
952 let header = header_from_tokens(&spellings);
953 if header.is_none() {
954 let at = expanded.first().map_or(hash, |t| t.report_span());
955 self.bad_header(at);
956 }
957 header
958 }
959
960 fn bad_operand(&mut self, tok: Tok, at: Span, interner: &Interner) {
962 self.diagnostics.push(
963 Diagnostic::error(
964 format!("expected an identifier as the operand of `{}`", spelling(tok, interner)),
965 at,
966 )
967 .with_code("E0345"),
968 );
969 }
970
971 fn bad_header(&mut self, at: Span) {
972 self.diagnostics.push(
973 Diagnostic::error("expected a file name in `<>` or `\"\"`", at).with_code("E0343"),
974 );
975 }
976
977 fn open(&mut self, span: Span, value: bool) {
979 let enclosing_live = self.live();
980 self.conds.push(Cond {
981 span,
982 live: enclosing_live && value,
983 taken: value,
984 enclosing_live,
985 seen_else: false,
986 });
987 }
988
989 fn elif(
990 &mut self,
991 name: Option<Symbol>,
992 rest: &[PpToken],
993 hash: Span,
994 cx: &mut Context<'_>,
995 names: &Names,
996 ) {
997 let Some(top) = self.conds.last() else {
998 self.stray("elif", hash);
999 return;
1000 };
1001 if top.seen_else {
1002 self.diagnostics
1003 .push(Diagnostic::error("`#elif` after `#else`", hash).with_code("E0333"));
1004 return;
1005 }
1006 let (enclosing_live, already_taken) = (top.enclosing_live, top.taken);
1009 let consider = enclosing_live && !already_taken;
1010 let value = if !consider {
1011 false
1012 } else if name == Some(names.elif) {
1013 self.eval(rest, hash, cx, names)
1014 } else {
1015 self.defined_check(rest, hash, name == Some(names.elifdef), names)
1016 };
1017 let top = self.conds.last_mut().expect("checked above and nothing popped");
1018 top.live = consider && value;
1019 top.taken = already_taken || value;
1020 }
1021
1022 fn branch_else(&mut self, rest: &[PpToken], hash: Span) {
1023 let Some(top) = self.conds.last_mut() else {
1024 self.stray("else", hash);
1025 return;
1026 };
1027 if top.seen_else {
1028 self.diagnostics.push(Diagnostic::error("a second `#else`", hash).with_code("E0333"));
1029 return;
1030 }
1031 top.live = top.enclosing_live && !top.taken;
1032 top.taken = true;
1033 top.seen_else = true;
1034 let enclosing_live = top.enclosing_live;
1035 if enclosing_live {
1036 self.extra_tokens(rest, "#else");
1037 }
1038 }
1039
1040 fn endif(&mut self, rest: &[PpToken], hash: Span) {
1041 if self.conds.pop().is_none() {
1042 self.stray("endif", hash);
1043 return;
1044 }
1045 if self.live() {
1046 self.extra_tokens(rest, "#endif");
1047 }
1048 }
1049
1050 fn stray(&mut self, what: &str, hash: Span) {
1051 self.diagnostics
1052 .push(Diagnostic::error(format!("`#{what}` without `#if`"), hash).with_code("E0334"));
1053 }
1054
1055 fn extra_tokens(&mut self, rest: &[PpToken], what: &str) {
1060 if let Some(first) = rest.first() {
1061 self.diagnostics.push(
1062 Diagnostic::warning(format!("extra tokens after `{what}`"), first.span)
1063 .with_code("W0330"),
1064 );
1065 }
1066 }
1067
1068 fn eval(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>, names: &Names) -> bool {
1070 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
1071 let line = self.resolve_defined(line, cx.interner, names);
1078 let line = self.resolve_has(line, cx, names, Pass::Headers);
1083 let condition = Condition { defined: names.defined, report: cx.pedantic };
1084 let line =
1085 self.expander.expand_condition(line, &self.macros, cx.interner, cx.sources, condition);
1086 self.diagnostics.append(&mut self.expander.take_diagnostics());
1087 let line = self.resolve_defined(line, cx.interner, names);
1088 let line = self.resolve_has(line, cx, names, Pass::Rest);
1089 cond::evaluate(&line, cx.interner, &mut self.diagnostics, hash)
1090 }
1091
1092 fn resolve_has(
1097 &mut self,
1098 line: Vec<Tok>,
1099 cx: &mut Context<'_>,
1100 names: &Names,
1101 pass: Pass,
1102 ) -> Vec<Tok> {
1103 if !line.iter().any(|t| t.ident().is_some_and(|n| names.has.op(n).is_some())) {
1104 return line;
1105 }
1106 let mut out = Vec::with_capacity(line.len());
1107 let mut at = 0;
1108 while at < line.len() {
1109 let tok = line[at];
1110 let op = tok.ident().and_then(|n| names.has.op(n));
1111 let Some(op) = op.filter(|op| pass.answers(*op)) else {
1112 if pass == Pass::Text && op.is_some_and(Op::is_header) {
1113 self.outside_a_directive(tok, cx);
1114 }
1115 out.push(tok);
1116 at += 1;
1117 continue;
1118 };
1119 let Some((operand, after)) = arguments(&line, at + 1) else {
1120 if pass != Pass::Headers {
1124 self.diagnostics.push(
1125 Diagnostic::error(
1126 format!("expected `(` after `{}`", spelling(tok, cx.interner)),
1127 tok.report_span(),
1128 )
1129 .with_code("E0345"),
1130 );
1131 }
1132 out.push(tok);
1133 at += 1;
1134 continue;
1135 };
1136 at = after;
1137 let value = self.ask(op, operand, tok, cx);
1140 let sym = cx.interner.intern(&value.to_string());
1141 out.push(Tok::synthetic(PpTokenKind::Number, Some(sym), tok.flags, tok.report_span()));
1142 }
1143 out
1144 }
1145
1146 fn outside_a_directive(&mut self, tok: Tok, cx: &Context<'_>) {
1154 self.diagnostics.push(
1155 Diagnostic::error(
1156 format!(
1157 "`{}` used outside of a preprocessing directive",
1158 spelling(tok, cx.interner)
1159 ),
1160 tok.report_span(),
1161 )
1162 .with_code("E0350"),
1163 );
1164 }
1165
1166 fn ask(&mut self, op: Op, operand: &[Tok], tok: Tok, cx: &mut Context<'_>) -> u32 {
1168 let at = operand.first().map_or(tok.report_span(), |t| t.report_span());
1169 match op {
1170 Op::Include | Op::IncludeNext => {
1171 let spellings: Vec<&str> =
1172 operand.iter().map(|t| spelling(*t, cx.interner)).collect();
1173 let Some(header) = header_from_tokens(&spellings) else {
1174 self.bad_header(at);
1175 return 0;
1176 };
1177 u32::from(self.find(&header, op == Op::IncludeNext, cx).is_some())
1178 }
1179 Op::Embed => {
1180 let Some(used) = embed::header_length(operand) else {
1185 self.bad_header(at);
1186 return 0;
1187 };
1188 let header = if operand[0].kind == PpTokenKind::HeaderName {
1189 header_from_token(spelling(operand[0], cx.interner))
1190 } else {
1191 let spellings: Vec<&str> =
1192 operand[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
1193 header_from_tokens(&spellings)
1194 };
1195 let Some(header) = header else {
1196 self.bad_header(at);
1197 return 0;
1198 };
1199 let Some(params) = self.embed_params(&operand[used..], at, cx) else {
1204 return 0;
1205 };
1206 match self.find(&header, false, cx) {
1207 None => 0,
1208 Some(found) => {
1209 let taken = params.taken(found.bytes.as_slice().len() as u64);
1210 if taken == 0 { 2 } else { 1 }
1211 }
1212 }
1213 }
1214 Op::BuildingModule => {
1215 if attribute_name(operand, cx.interner).is_none() {
1216 self.bad_operand(tok, at, cx.interner);
1217 }
1218 0
1225 }
1226 Op::Table(kind) => {
1227 let Some(name) = attribute_name(operand, cx.interner) else {
1228 self.bad_operand(tok, at, cx.interner);
1229 return 0;
1230 };
1231 match kind {
1232 Kind::Attribute => rucc_gnu::has_attribute(name),
1233 Kind::CAttribute => rucc_gnu::has_c_attribute(name),
1234 Kind::Builtin => rucc_gnu::has_builtin(name),
1235 Kind::Feature => rucc_gnu::has_feature(name),
1236 Kind::Extension => rucc_gnu::has_extension(name),
1237 }
1238 }
1239 }
1240 }
1241
1242 fn resolve_defined(
1244 &mut self,
1245 line: Vec<Tok>,
1246 interner: &mut Interner,
1247 names: &Names,
1248 ) -> Vec<Tok> {
1249 if !line.iter().any(|t| t.ident() == Some(names.defined)) {
1250 return line;
1251 }
1252 let mut out = Vec::with_capacity(line.len());
1253 let mut at = 0;
1254 while at < line.len() {
1255 let tok = line[at];
1256 if tok.ident() != Some(names.defined) {
1257 out.push(tok);
1258 at += 1;
1259 continue;
1260 }
1261 let parenthesised = line.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1262 let name_at = if parenthesised { at + 2 } else { at + 1 };
1263 let name = line.get(name_at).and_then(|t| t.ident());
1264 let Some(name) = name else {
1265 self.diagnostics.push(
1266 Diagnostic::error("`defined` without a macro name", tok.report_span())
1267 .with_code("E0335"),
1268 );
1269 out.push(tok);
1270 at += 1;
1271 continue;
1272 };
1273 at = name_at + 1;
1274 if parenthesised {
1275 if line.get(at).is_some_and(|t| t.is(Punct::RParen)) {
1276 at += 1;
1277 } else {
1278 self.diagnostics.push(
1279 Diagnostic::error("expected `)` after `defined`", tok.report_span())
1280 .with_code("E0335"),
1281 );
1282 }
1283 }
1284 let value = self.macros.is_defined(name) || names.has.op(name).is_some();
1288 out.push(number(value, tok.flags, tok.report_span(), interner));
1289 }
1290 out
1291 }
1292
1293 fn defined_check(
1295 &mut self,
1296 rest: &[PpToken],
1297 hash: Span,
1298 want_defined: bool,
1299 names: &Names,
1300 ) -> bool {
1301 let Some(name) = rest.first().and_then(ident_of) else {
1302 self.diagnostics.push(
1303 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1304 .with_code("E0336"),
1305 );
1306 return false;
1307 };
1308 self.extra_tokens(&rest[1..], if want_defined { "#ifdef" } else { "#ifndef" });
1309 let defined = self.macros.is_defined(name) || names.has.op(name).is_some();
1310 defined == want_defined
1311 }
1312
1313 fn undef(&mut self, rest: &[PpToken], hash: Span, interner: &Interner) {
1314 let Some(name) = rest.first().and_then(ident_of) else {
1315 self.diagnostics.push(
1316 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1317 .with_code("E0336"),
1318 );
1319 return;
1320 };
1321 let text = interner.resolve(name);
1326 if text == "defined" {
1327 self.diagnostics.push(
1328 Diagnostic::error(format!("`{text}` cannot be undefined"), rest[0].span)
1329 .with_code("E0337"),
1330 );
1331 return;
1332 }
1333 self.macros.undef(name);
1334 self.extra_tokens(&rest[1..], "#undef");
1335 }
1336
1337 fn message(&mut self, rest: &[PpToken], hash: Span, fatal: bool, interner: &Interner) {
1339 let text = spell_line(rest, interner);
1340 let span = rest.first().map_or(hash, |t| t.span.to(last_span(rest)));
1341 let diag = if fatal {
1342 Diagnostic::error(text, span).with_code("E0338")
1343 } else {
1344 Diagnostic::warning(text, span).with_code("W0331")
1345 };
1346 self.diagnostics.push(diag);
1347 }
1348
1349 fn line(&mut self, rest: &[PpToken], hash: Span, at: usize, cx: &mut Context<'_>) {
1354 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
1355 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
1356 self.diagnostics.append(&mut self.expander.take_diagnostics());
1357 let interner = &mut *cx.interner;
1358
1359 let number_text = line
1360 .first()
1361 .filter(|t| t.kind == PpTokenKind::Number)
1362 .and_then(|t| t.value)
1363 .map(|v| interner.resolve(v));
1364 let Some(parsed) = number_text.and_then(|t| t.parse::<u64>().ok()) else {
1365 self.diagnostics.push(
1366 Diagnostic::error(
1367 "`#line` needs a decimal line number",
1368 line.first().map_or(hash, |t| t.report_span()),
1369 )
1370 .with_code("E0339"),
1371 );
1372 return;
1373 };
1374 if parsed == 0 || parsed > 2_147_483_647 {
1377 self.diagnostics.push(
1378 Diagnostic::error("`#line` number is out of range", line[0].report_span())
1379 .with_code("E0339"),
1380 );
1381 return;
1382 }
1383
1384 let mut file = None;
1385 if let Some(second) = line.get(1) {
1386 if second.kind == PpTokenKind::StringLit {
1387 file = second.value;
1388 } else {
1389 self.diagnostics.push(
1390 Diagnostic::error(
1391 "`#line` file name must be a string literal",
1392 second.report_span(),
1393 )
1394 .with_code("E0339"),
1395 );
1396 return;
1397 }
1398 }
1399 if let Some(extra) = line.get(2) {
1400 self.diagnostics.push(
1401 Diagnostic::warning("extra tokens after `#line`", extra.report_span())
1402 .with_code("W0330"),
1403 );
1404 }
1405 #[expect(
1406 clippy::cast_possible_truncation,
1407 reason = "the range check above keeps this inside i32, let alone u32"
1408 )]
1409 let number = parsed as u32;
1410 self.lines.push(LineDirective { span: hash, line: number, file, at });
1411 let name = file.map(|v| destringize(cx.interner.resolve(v)));
1412 cx.sources.set_presumed(hash.lo, number, name);
1413 }
1414
1415 fn line_marker(&mut self, body: &[PpToken], hash: Span, at: usize, cx: &mut Context<'_>) {
1432 let Some(number) = decimal(&body[0], cx.interner) else { return };
1433 let mut rest = &body[1..];
1434 let mut file = None;
1435 if let Some(first) = rest.first().filter(|t| t.kind == PpTokenKind::StringLit) {
1436 file = first.value;
1437 rest = &rest[1..];
1438 }
1439
1440 let (mut entering, mut leaving) = (false, false);
1441 for flag in rest {
1442 match decimal(flag, cx.interner) {
1443 Some(1) => entering = true,
1444 Some(2) => leaving = true,
1445 Some(3 | 4) => {}
1446 _ => {
1447 let text = spell_line(std::slice::from_ref(flag), cx.interner);
1448 self.diagnostics.push(
1449 Diagnostic::error(
1450 format!("invalid flag `{text}` in line directive"),
1451 flag.span,
1452 )
1453 .with_code("E0339"),
1454 );
1455 return;
1456 }
1457 }
1458 }
1459
1460 let name = file.map(|v| destringize(cx.interner.resolve(v)));
1461 if leaving {
1462 if let Some(name) = &name {
1463 if !self.leave_marker(name) {
1464 self.diagnostics.push(
1465 Diagnostic::warning(
1466 format!("file `{name}` linemarker ignored due to incorrect nesting"),
1467 last_span(body),
1468 )
1469 .with_code("W0330"),
1470 );
1471 return;
1472 }
1473 } else {
1474 self.markers.pop();
1475 }
1476 }
1477 if entering {
1478 let here = cx.sources.presumed(hash.lo).map(|loc| loc.name.to_owned());
1479 self.markers.push(here.unwrap_or_default());
1480 }
1481
1482 self.lines.push(LineDirective { span: hash, line: number, file, at });
1483 cx.sources.set_presumed(hash.lo, number, name);
1484 }
1485
1486 fn leave_marker(&mut self, name: &str) -> bool {
1500 if let Some(at) = self.markers.iter().rposition(|outer| outer == name) {
1501 self.markers.truncate(at);
1502 return true;
1503 }
1504 let found = self.stack.iter().rev().skip(1).any(|f| f.path.as_os_str() == name);
1507 if found {
1508 self.markers.clear();
1509 }
1510 found
1511 }
1512
1513 fn pragma_operator(
1519 &mut self,
1520 expanded: Vec<Tok>,
1521 out: &mut Vec<Tok>,
1522 interner: &mut Interner,
1523 names: &Names,
1524 ) {
1525 if !expanded.iter().any(|t| t.ident() == Some(names.pragma_op)) {
1526 out.extend(expanded);
1527 return;
1528 }
1529 let mut at = 0;
1530 let mut ends_a_line = false;
1535 while at < expanded.len() {
1536 let mut tok = expanded[at];
1537 if tok.ident() != Some(names.pragma_op) {
1538 if ends_a_line {
1539 tok.flags = tok.flags.with(TokenFlags::START_OF_LINE);
1540 ends_a_line = false;
1541 }
1542 out.push(tok);
1543 at += 1;
1544 continue;
1545 }
1546 let open = expanded.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1547 let text = expanded.get(at + 2).filter(|t| t.kind == PpTokenKind::StringLit);
1548 let close = expanded.get(at + 3).is_some_and(|t| t.is(Punct::RParen));
1549 let (Some(text), true, true) = (text, open, close) else {
1550 self.diagnostics.push(
1551 Diagnostic::error("`_Pragma` takes a single string literal", tok.report_span())
1552 .with_code("E0340"),
1553 );
1554 out.push(tok);
1555 at += 1;
1556 continue;
1557 };
1558 let literal = text.value.map(|v| interner.resolve(v)).unwrap_or_default();
1559 let body = destringize(literal);
1560 self.emit_pragma(&body, tok, out, interner, names);
1561 ends_a_line = true;
1562 at += 4;
1563 }
1564 }
1565
1566 fn emit_pragma(
1568 &mut self,
1569 body: &str,
1570 at: Tok,
1571 out: &mut Vec<Tok>,
1572 interner: &mut Interner,
1573 names: &Names,
1574 ) {
1575 let span = at.report_span();
1576 let (tokens, diagnostics) = tokenize(body.as_bytes(), 0, Options::new(), interner);
1577 self.diagnostics.extend(
1580 diagnostics
1581 .into_iter()
1582 .map(|d| Diagnostic::new(d.severity, d.message, span).with_code("E0340")),
1583 );
1584 let tokens: Vec<PpToken> = tokens.into_iter().filter(|t| !t.is_eof()).collect();
1585 if self.macro_stack_pragma(&tokens, span, interner, names) {
1589 return;
1590 }
1591 out.push(Tok::synthetic(
1592 PpTokenKind::Punct(Punct::Hash),
1593 None,
1594 TokenFlags::START_OF_LINE,
1595 span,
1596 ));
1597 out.push(Tok::synthetic(PpTokenKind::Ident, Some(names.pragma), TokenFlags::EMPTY, span));
1598 for (at, t) in tokens.into_iter().enumerate() {
1602 let spaced = at == 0 || t.flags.has(TokenFlags::LEADING_SPACE);
1605 let flags = if spaced {
1606 TokenFlags::EMPTY.with(TokenFlags::LEADING_SPACE)
1607 } else {
1608 TokenFlags::EMPTY
1609 };
1610 out.push(Tok::synthetic(t.kind, t.value, flags, span));
1611 }
1612 }
1613}
1614
1615fn guard_opener(body: &[PpToken], names: &Names) -> Option<Symbol> {
1620 let name = ident_of(body.first()?)?;
1621 let rest = &body[1..];
1622 if name == names.ifndef {
1623 let [only] = rest else {
1624 return None;
1625 };
1626 return ident_of(only);
1627 }
1628 if name != names.r#if {
1629 return None;
1630 }
1631 let [bang, defined, tail @ ..] = rest else {
1632 return None;
1633 };
1634 if bang.punct() != Some(Punct::Bang) || ident_of(defined) != Some(names.defined) {
1635 return None;
1636 }
1637 match tail {
1638 [only] => ident_of(only),
1639 [open, only, close]
1640 if open.punct() == Some(Punct::LParen) && close.punct() == Some(Punct::RParen) =>
1641 {
1642 ident_of(only)
1643 }
1644 _ => None,
1645 }
1646}
1647
1648fn is_alternative(name: Option<Symbol>, names: &Names) -> bool {
1650 let Some(name) = name else { return false };
1651 name == names.r#else || name == names.elif || name == names.elifdef || name == names.elifndef
1652}
1653
1654fn is_include(name: Option<Symbol>, names: &Names) -> bool {
1656 name == Some(names.include) || name == Some(names.include_next) || name == Some(names.embed)
1657}
1658
1659fn is_directive(tok: PpToken) -> bool {
1661 tok.flags.has(TokenFlags::START_OF_LINE) && tok.punct() == Some(Punct::Hash)
1662}
1663
1664fn ident_of(tok: &PpToken) -> Option<Symbol> {
1665 match tok.kind {
1666 PpTokenKind::Ident => tok.value,
1667 _ => None,
1668 }
1669}
1670
1671fn last_span(tokens: &[PpToken]) -> Span {
1672 tokens.last().map_or(Span::DUMMY, |t| t.span)
1673}
1674
1675fn decimal(tok: &PpToken, interner: &Interner) -> Option<u32> {
1681 if tok.kind != PpTokenKind::Number {
1682 return None;
1683 }
1684 let text = interner.resolve(tok.value?);
1685 if text.is_empty() || !text.bytes().all(|b| b.is_ascii_digit()) {
1686 return None;
1687 }
1688 text.parse::<u32>().ok().filter(|n| *n <= 2_147_483_647)
1691}
1692
1693fn number(value: bool, flags: TokenFlags, span: Span, interner: &mut Interner) -> Tok {
1695 let sym = interner.intern(if value { "1" } else { "0" });
1696 Tok::synthetic(PpTokenKind::Number, Some(sym), flags, span)
1697}
1698
1699fn spell_line(tokens: &[PpToken], interner: &Interner) -> String {
1701 let mut out = String::new();
1702 for (index, tok) in tokens.iter().enumerate() {
1703 if index > 0 && tok.flags.has(TokenFlags::LEADING_SPACE) {
1704 out.push(' ');
1705 }
1706 match tok.value {
1707 Some(sym) => out.push_str(interner.resolve(sym)),
1708 None => {
1709 if let Some(p) = tok.punct() {
1710 out.push_str(p.as_str());
1711 }
1712 }
1713 }
1714 }
1715 out
1716}
1717
1718fn identifier_in(text: PpToken, interner: &mut Interner) -> Option<Symbol> {
1723 let literal = interner.resolve(text.value?).to_string();
1724 let (tokens, _) = tokenize(destringize(&literal).as_bytes(), 0, Options::new(), interner);
1725 let mut real = tokens.into_iter().filter(|t| !t.is_eof());
1726 let first = real.next()?;
1727 if first.kind != PpTokenKind::Ident || real.next().is_some() {
1728 return None;
1729 }
1730 first.value
1731}
1732
1733fn destringize(literal: &str) -> String {
1738 let body = literal
1739 .trim_start_matches(['L', 'u', 'U', '8'])
1740 .strip_prefix('"')
1741 .and_then(|s| s.strip_suffix('"'))
1742 .unwrap_or(literal);
1743 let mut out = String::with_capacity(body.len());
1744 let mut chars = body.chars();
1745 while let Some(c) = chars.next() {
1746 if c != '\\' {
1747 out.push(c);
1748 continue;
1749 }
1750 match chars.next() {
1751 Some('"') => out.push('"'),
1752 Some('\\') => out.push('\\'),
1753 Some(other) => {
1754 out.push('\\');
1755 out.push(other);
1756 }
1757 None => out.push('\\'),
1758 }
1759 }
1760 out
1761}
1762
1763fn arguments(line: &[Tok], at: usize) -> Option<(&[Tok], usize)> {
1769 if !line.get(at)?.is(Punct::LParen) {
1770 return None;
1771 }
1772 let mut depth = 1u32;
1773 let mut end = at + 1;
1774 while end < line.len() {
1775 if line[end].is(Punct::LParen) {
1776 depth += 1;
1777 } else if line[end].is(Punct::RParen) {
1778 depth -= 1;
1779 if depth == 0 {
1780 return Some((&line[at + 1..end], end + 1));
1781 }
1782 }
1783 end += 1;
1784 }
1785 None
1786}
1787
1788fn attribute_name<'i>(operand: &[Tok], interner: &'i Interner) -> Option<&'i str> {
1794 let name = match operand {
1795 [one] => one,
1796 [_, scope, name] if scope.is(Punct::ColonColon) => name,
1797 _ => return None,
1798 };
1799 name.ident().map(|sym| interner.resolve(sym))
1800}
1801
1802#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1809enum Pass {
1810 Headers,
1812 Rest,
1815 Text,
1817}
1818
1819impl Pass {
1820 fn answers(self, op: Op) -> bool {
1822 match self {
1823 Pass::Headers => op.is_header(),
1824 Pass::Rest => true,
1825 Pass::Text => !op.is_header(),
1826 }
1827 }
1828}
1829
1830#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1832enum Op {
1833 Include,
1835 IncludeNext,
1837 Embed,
1840 BuildingModule,
1842 Table(Kind),
1844}
1845
1846impl Op {
1847 fn is_header(self) -> bool {
1849 matches!(self, Op::Include | Op::IncludeNext | Op::Embed)
1850 }
1851}
1852
1853struct HasOps {
1858 ops: [(Symbol, Op); 9],
1859 range: (Symbol, Symbol),
1866}
1867
1868impl HasOps {
1869 fn new(interner: &mut Interner) -> HasOps {
1870 let ops = [
1871 (interner.intern("__has_include"), Op::Include),
1872 (interner.intern("__has_include_next"), Op::IncludeNext),
1873 (interner.intern("__has_embed"), Op::Embed),
1874 (interner.intern("__has_attribute"), Op::Table(Kind::Attribute)),
1875 (interner.intern("__has_c_attribute"), Op::Table(Kind::CAttribute)),
1876 (interner.intern("__has_builtin"), Op::Table(Kind::Builtin)),
1877 (interner.intern("__has_feature"), Op::Table(Kind::Feature)),
1878 (interner.intern("__has_extension"), Op::Table(Kind::Extension)),
1879 (interner.intern("__building_module"), Op::BuildingModule),
1880 ];
1881 let mut range = (ops[0].0, ops[0].0);
1882 for &(sym, _) in &ops {
1883 range = (range.0.min(sym), range.1.max(sym));
1884 }
1885 HasOps { ops, range }
1886 }
1887
1888 #[inline]
1890 fn op(&self, name: Symbol) -> Option<Op> {
1891 if name < self.range.0 || name > self.range.1 {
1892 return None;
1893 }
1894 self.ops.iter().find(|(sym, _)| *sym == name).map(|(_, op)| *op)
1895 }
1896}
1897
1898struct Names {
1904 define: Symbol,
1905 undef: Symbol,
1906 r#if: Symbol,
1907 ifdef: Symbol,
1908 ifndef: Symbol,
1909 elif: Symbol,
1910 elifdef: Symbol,
1911 elifndef: Symbol,
1912 r#else: Symbol,
1913 endif: Symbol,
1914 line: Symbol,
1915 error: Symbol,
1916 warning: Symbol,
1917 pragma: Symbol,
1918 include: Symbol,
1919 include_next: Symbol,
1920 embed: Symbol,
1921 defined: Symbol,
1922 once: Symbol,
1923 push_macro: Symbol,
1924 pop_macro: Symbol,
1925 pragma_op: Symbol,
1926 has: HasOps,
1927}
1928
1929impl Names {
1930 fn new(interner: &mut Interner) -> Names {
1931 Names {
1932 define: interner.intern("define"),
1933 undef: interner.intern("undef"),
1934 r#if: interner.intern("if"),
1935 ifdef: interner.intern("ifdef"),
1936 ifndef: interner.intern("ifndef"),
1937 elif: interner.intern("elif"),
1938 elifdef: interner.intern("elifdef"),
1939 elifndef: interner.intern("elifndef"),
1940 r#else: interner.intern("else"),
1941 endif: interner.intern("endif"),
1942 line: interner.intern("line"),
1943 error: interner.intern("error"),
1944 warning: interner.intern("warning"),
1945 pragma: interner.intern("pragma"),
1946 include: interner.intern("include"),
1947 include_next: interner.intern("include_next"),
1948 embed: interner.intern("embed"),
1949 defined: interner.intern("defined"),
1950 once: interner.intern("once"),
1951 push_macro: interner.intern("push_macro"),
1952 pop_macro: interner.intern("pop_macro"),
1953 pragma_op: interner.intern("_Pragma"),
1954 has: HasOps::new(interner),
1955 }
1956 }
1957}
1958
1959#[cfg(test)]
1960mod tests {
1961 use rucc_diag::{Severity, SourceMap};
1962 use rucc_session::{MemoryFileSystem, SearchPath};
1963
1964 use super::*;
1965 use rucc_session::Std;
1966
1967 use crate::predef::Timestamp;
1968
1969 fn slashes(text: &str) -> String {
1977 text.replace("\\\\", "/").replace('\\', "/")
1978 }
1979
1980 struct Run {
1981 interner: Interner,
1982 sources: SourceMap,
1983 fs: MemoryFileSystem,
1984 search: SearchPath,
1985 pp: Preprocessor,
1986 pedantic: bool,
1988 }
1989
1990 impl Run {
1991 fn new() -> Run {
1992 Run {
1993 interner: Interner::new(),
1994 sources: SourceMap::new(),
1995 fs: MemoryFileSystem::new(),
1996 search: SearchPath::new(),
1997 pp: Preprocessor::new(),
1998 pedantic: false,
1999 }
2000 }
2001
2002 fn pedantic() -> Run {
2004 Run { pedantic: true, ..Run::new() }
2005 }
2006
2007 fn mapping(map: &[(&str, &str)]) -> Run {
2009 let mut list = PrefixMap::new();
2010 for (old, new) in map {
2011 list.push(*old, *new);
2012 }
2013 Run { pp: Preprocessor::with_prefix_map(list), ..Run::new() }
2014 }
2015
2016 fn file(&mut self, path: &str, contents: &str) {
2018 self.fs.insert(path, contents.as_bytes().to_vec());
2019 }
2020
2021 fn bytes(&mut self, path: &str, contents: &[u8]) {
2024 self.fs.insert(path, contents.to_vec());
2025 }
2026
2027 fn dir(&mut self, path: &str) {
2029 self.search.push_bracket(path);
2030 }
2031
2032 fn predefine(&mut self, triple: &str, opts: &Predef) {
2034 let target = TargetInfo::new(triple.parse().expect("a supported triple"));
2035 let mut cx =
2036 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
2037 self.pp.predefine(&target, opts, &mut cx).expect("the map has room");
2038 }
2039
2040 fn go(&mut self, src: &str) -> String {
2042 self.go_named("/main.c", src)
2043 }
2044
2045 fn raw(&mut self, src: &str) -> Vec<Tok> {
2047 let file = self.sources.add("/main.c", src.as_bytes().to_vec()).expect("room");
2048 let pedantic = self.pedantic;
2049 let mut cx =
2050 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
2051 cx.pedantic = pedantic;
2052 self.pp.run(file, &mut cx)
2053 }
2054
2055 fn preinclude(&mut self, files: &[Preinclude]) -> String {
2057 let mut out = Vec::new();
2058 {
2059 let mut cx =
2060 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
2061 self.pp.preinclude(files, &mut out, &mut cx).expect("the map has room");
2062 }
2063 self.spell(&out)
2064 }
2065
2066 fn go_named(&mut self, path: &str, src: &str) -> String {
2068 let file = self.sources.add(path, src.as_bytes().to_vec()).expect("the map has room");
2069 let pedantic = self.pedantic;
2070 let out = {
2071 let mut cx =
2072 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
2073 cx.pedantic = pedantic;
2074 self.pp.run(file, &mut cx)
2075 };
2076 self.spell(&out)
2077 }
2078
2079 fn spell(&self, out: &[Tok]) -> String {
2081 let mut text = String::new();
2082 for (at, tok) in out.iter().enumerate() {
2083 let spaced = tok.flags.has(TokenFlags::LEADING_SPACE)
2084 || tok.flags.has(TokenFlags::START_OF_LINE);
2085 if at > 0 && spaced {
2086 text.push(' ');
2087 }
2088 match tok.kind {
2089 PpTokenKind::Punct(p) => text.push_str(p.as_str()),
2090 _ => text.push_str(
2091 self.interner.resolve(tok.value.expect("every non-punctuator interns")),
2092 ),
2093 }
2094 }
2095 text
2096 }
2097
2098 fn files(&self) -> usize {
2102 self.sources.files().len()
2103 }
2104
2105 fn messages(&mut self) -> Vec<String> {
2106 self.pp.take_diagnostics().into_iter().map(|d| d.message).collect()
2107 }
2108
2109 fn severities(&mut self) -> Vec<Severity> {
2110 self.pp.diagnostics().iter().map(|d| d.severity).collect()
2111 }
2112 }
2113
2114 fn clean(src: &str) -> String {
2115 let mut run = Run::new();
2116 let text = run.go(src);
2117 assert!(run.messages().is_empty(), "expected no diagnostics from {src:?}");
2118 text
2119 }
2120
2121 #[test]
2122 fn a_taken_branch_is_kept_and_the_other_is_not() {
2123 assert_eq!(clean("#if 1\nyes\n#else\nno\n#endif\n"), "yes");
2124 assert_eq!(clean("#if 0\nyes\n#else\nno\n#endif\n"), "no");
2125 }
2126
2127 #[test]
2128 fn ifdef_and_ifndef_ask_the_macro_table() {
2129 assert_eq!(clean("#define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
2130 assert_eq!(clean("#ifdef F\nyes\n#endif\n"), "");
2131 assert_eq!(clean("#ifndef F\nyes\n#endif\n"), "yes");
2132 assert_eq!(clean("#define F 1\n#if 0\na\n#elifdef F\nb\n#endif\n"), "b");
2134 assert_eq!(clean("#if 0\na\n#elifndef F\nb\n#endif\n"), "b");
2135 }
2136
2137 #[test]
2138 fn only_the_first_true_branch_of_a_chain_is_taken() {
2139 assert_eq!(clean("#if 0\na\n#elif 1\nb\n#elif 1\nc\n#else\nd\n#endif\n"), "b");
2140 assert_eq!(clean("#if 0\na\n#elif 0\nb\n#else\nc\n#endif\n"), "c");
2141 }
2142
2143 #[test]
2144 fn a_branch_after_one_that_was_taken_is_not_evaluated() {
2145 assert_eq!(clean("#if 1\na\n#elif 1/0\nb\n#endif\n"), "a");
2148 }
2149
2150 #[test]
2151 fn a_skipped_region_is_not_read_for_anything_but_nesting() {
2152 let src = "#if 0\nthis is not C at all\n#frobnicate\n#define\n#if 1\ninner\n#endif\n#endif\nafter\n";
2154 assert_eq!(clean(src), "after");
2155 }
2156
2157 #[test]
2158 fn nesting_inside_a_dead_branch_stays_balanced() {
2159 let src = "#if 0\n#ifdef X\na\n#else\nb\n#endif\n#else\nc\n#endif\n";
2160 assert_eq!(clean(src), "c");
2161 }
2162
2163 #[test]
2164 fn defined_works_in_both_spellings_and_before_expansion() {
2165 assert_eq!(clean("#define F 0\n#if defined F\nyes\n#endif\n"), "yes");
2166 assert_eq!(clean("#define F 0\n#if defined(F)\nyes\n#endif\n"), "yes");
2167 assert_eq!(clean("#if defined(F)\nyes\n#endif\n"), "");
2168 assert_eq!(clean("#define F 0\n#if defined F && !F\nyes\n#endif\n"), "yes");
2171 }
2172
2173 #[test]
2174 fn a_macro_may_write_the_defined_operator_itself() {
2175 assert_eq!(clean("#define F 0\n#define D defined F\n#if D\nyes\n#endif\n"), "yes");
2179 assert_eq!(clean("#define F 0\n#define D defined(F)\n#if D\nyes\n#endif\n"), "yes");
2180 assert_eq!(clean("#define D defined(F)\n#if D\nyes\n#else\nno\n#endif\n"), "no");
2181 }
2182
2183 #[test]
2184 fn the_name_a_macro_wrote_the_defined_operator_about_is_not_expanded() {
2185 assert_eq!(clean("#define F 1\n#define D defined(F)\n#if D\nyes\n#endif\n"), "yes");
2190 assert_eq!(clean("#define F\n#define D defined(F)\n#if D\nyes\n#endif\n"), "yes");
2194 let src = "#define MARK_lrotl\n#define HAVE(n) defined(MARK_ ## n)\n\
2196 #if HAVE(lrotl)\nyes\n#endif\n";
2197 assert_eq!(clean(src), "yes");
2198 }
2199
2200 #[test]
2201 fn a_defined_a_macro_wrote_is_reported_under_pedantic() {
2202 let mut run = Run::pedantic();
2203 assert_eq!(run.go("#define F 1\n#define D defined(F)\n#if D\nyes\n#endif\n"), "yes");
2204 assert_eq!(run.messages(), vec!["this use of `defined` may not be portable".to_owned()]);
2205 let mut run = Run::pedantic();
2208 assert_eq!(run.go("#define F 1\n#if defined(F)\nyes\n#endif\n"), "yes");
2209 assert!(run.messages().is_empty());
2210 assert_eq!(clean("#define F 1\n#define D defined(F)\n#if D\nyes\n#endif\n"), "yes");
2213 }
2214
2215 #[test]
2216 fn a_defined_a_macro_wrote_badly_is_still_an_error() {
2217 let mut run = Run::new();
2221 run.go("#define D defined\n#if D\nyes\n#endif\n");
2222 assert_eq!(run.messages(), vec!["`defined` without a macro name".to_owned()]);
2223 let mut run = Run::new();
2226 run.go("#define D defined(1)\n#if D\nyes\n#endif\n");
2227 assert_eq!(run.messages()[0], "`defined` without a macro name");
2228 let mut run = Run::new();
2229 run.go("#define D defined(F\n#if D\nyes\n#endif\n");
2230 assert_eq!(run.messages(), vec!["expected `)` after `defined`".to_owned()]);
2231 let mut run = Run::new();
2236 run.go("#define F 0\n#define D(x) defined(x)\n#if D(F)\nyes\n#endif\n");
2237 assert_eq!(run.messages()[0], "`defined` without a macro name");
2238 }
2239
2240 #[test]
2241 fn an_identifier_that_survived_expansion_is_zero() {
2242 assert_eq!(clean("#if NOT_DEFINED_ANYWHERE\nyes\n#else\nno\n#endif\n"), "no");
2243 assert_eq!(clean("#if !NOT_DEFINED_ANYWHERE\nyes\n#endif\n"), "yes");
2244 }
2245
2246 #[test]
2247 fn short_circuiting_keeps_a_guarded_expression_safe() {
2248 assert_eq!(clean("#if defined(F) && 1/F\nyes\n#else\nno\n#endif\n"), "no");
2251 assert_eq!(clean("#if 1 ? 2 : 1/0\nyes\n#endif\n"), "yes");
2252 }
2253
2254 #[test]
2255 fn the_operators_have_the_precedence_they_do_in_c() {
2256 assert_eq!(clean("#if 1 + 2 * 3 == 7\nyes\n#endif\n"), "yes");
2257 assert_eq!(clean("#if (1 + 2) * 3 == 9\nyes\n#endif\n"), "yes");
2258 assert_eq!(clean("#if 1 << 4 == 16\nyes\n#endif\n"), "yes");
2259 assert_eq!(clean("#if -8 / 3 == -2\nyes\n#endif\n"), "yes");
2260 assert_eq!(clean("#if (0xff & 0x0f) == 15\nyes\n#endif\n"), "yes");
2261 }
2262
2263 #[test]
2264 fn an_unsigned_operand_makes_the_whole_comparison_unsigned() {
2265 assert_eq!(clean("#if -1 < 0u\nyes\n#else\nno\n#endif\n"), "no");
2269 assert_eq!(clean("#if -1 < 0\nyes\n#else\nno\n#endif\n"), "yes");
2270 }
2271
2272 #[test]
2273 fn character_constants_evaluate() {
2274 assert_eq!(clean("#if 'A' == 65\nyes\n#endif\n"), "yes");
2275 assert_eq!(clean("#if '\\n' == 10\nyes\n#endif\n"), "yes");
2276 }
2277
2278 #[test]
2279 fn a_macro_is_expanded_before_the_expression_is_evaluated() {
2280 assert_eq!(clean("#define V 3\n#if V > 2\nyes\n#endif\n"), "yes");
2281 assert_eq!(clean("#define M(a) ((a) * 2)\n#if M(3) == 6\nyes\n#endif\n"), "yes");
2282 }
2283
2284 #[test]
2285 fn an_invocation_may_span_lines_within_a_run_of_text() {
2286 assert_eq!(clean("#define M(a, b) a + b\nM(1,\n2)\n"), "1 + 2");
2287 }
2288
2289 #[test]
2290 fn a_comment_across_lines_inside_a_definition_does_not_end_it() {
2291 let src = "#define F(x) \\\n ((x) + \\\n/* runs\nonto two lines */ \\\n 1)\nF(2)\n";
2292 assert_eq!(clean(src), "((2) + 1)");
2293 assert_eq!(clean("#define G 1 /* a\nb */ + 2\nG\n"), "1 + 2");
2294 assert_eq!(clean("#/* a\n*/define H 4\nH\n"), "4");
2296 }
2297
2298 #[test]
2299 fn a_hash_after_a_comment_across_lines_is_a_directive_only_if_the_comment_began_the_line() {
2300 assert_eq!(clean("/* a\nb */ #define X 3\nX\n"), "3");
2301 assert!(clean("int x; /* a\nb */ #define Y 3\nY\n").ends_with(" 3 Y"));
2302 }
2303
2304 #[test]
2305 fn undef_removes_a_definition() {
2306 assert_eq!(clean("#define F 1\n#undef F\n#ifdef F\nyes\n#else\nno\n#endif\n"), "no");
2307 assert_eq!(clean("#undef NEVER_DEFINED\nok\n"), "ok");
2310 }
2311
2312 #[test]
2313 fn some_names_cannot_be_undefined() {
2314 let mut run = Run::new();
2315 run.go("#undef defined\n");
2316 assert_eq!(run.messages(), vec!["`defined` cannot be undefined".to_owned()]);
2317 }
2318
2319 #[test]
2320 fn a_name_that_only_looks_reserved_can_be_undefined() {
2321 let mut run = Run::new();
2322 let src = "#define __STDC_WANT_LIB_EXT1__ 0\n#undef __STDC_WANT_LIB_EXT1__\n\
2323 #ifndef __STDC_WANT_LIB_EXT1__\ngone\n#endif\n";
2324 assert_eq!(run.go(src), "gone");
2325 assert!(run.messages().is_empty(), "{:?}", run.messages());
2326 }
2327
2328 #[test]
2329 fn error_reports_the_rest_of_the_line() {
2330 let mut run = Run::new();
2331 run.go("#if 0\n#error not this one\n#else\n#error unsupported target\n#endif\n");
2332 assert_eq!(run.messages(), vec!["unsupported target".to_owned()]);
2333 }
2334
2335 #[test]
2336 fn warning_is_a_warning() {
2337 let mut run = Run::new();
2338 run.go("#warning this is fine\n");
2339 assert_eq!(run.severities(), vec![Severity::Warning]);
2340 assert_eq!(run.messages(), vec!["this is fine".to_owned()]);
2341 }
2342
2343 #[test]
2344 fn an_unterminated_conditional_is_reported() {
2345 let mut run = Run::new();
2346 assert_eq!(run.go("#if 1\nyes\n"), "yes");
2347 assert_eq!(run.messages(), vec!["unterminated `#if`".to_owned()]);
2348 }
2349
2350 #[test]
2351 fn a_conditional_without_an_if_is_reported() {
2352 let mut run = Run::new();
2353 run.go("#endif\n");
2354 assert_eq!(run.messages(), vec!["`#endif` without `#if`".to_owned()]);
2355
2356 let mut run = Run::new();
2357 run.go("#if 1\n#else\n#else\n#endif\n");
2358 assert_eq!(run.messages(), vec!["a second `#else`".to_owned()]);
2359
2360 let mut run = Run::new();
2361 run.go("#if 1\n#else\n#elif 1\n#endif\n");
2362 assert_eq!(run.messages(), vec!["`#elif` after `#else`".to_owned()]);
2363 }
2364
2365 #[test]
2366 fn tokens_after_endif_are_a_warning_rather_than_an_error() {
2367 let mut run = Run::new();
2370 assert_eq!(run.go("#if 1\nyes\n#endif FOO\n"), "yes");
2371 assert_eq!(run.severities(), vec![Severity::Warning]);
2372 assert_eq!(run.messages(), vec!["extra tokens after `#endif`".to_owned()]);
2373 }
2374
2375 #[test]
2376 fn the_null_directive_does_nothing() {
2377 assert_eq!(clean("#\na\n#\nb\n"), "a b");
2378 }
2379
2380 #[test]
2381 fn an_unknown_directive_is_an_error_when_the_region_is_live() {
2382 let mut run = Run::new();
2383 run.go("#frobnicate\n");
2384 assert_eq!(run.messages(), vec!["invalid preprocessing directive".to_owned()]);
2385 }
2386
2387 #[test]
2388 fn line_is_recorded_for_the_source_map() {
2389 let mut run = Run::new();
2390 run.go("#line 42 \"other.c\"\n");
2391 assert!(run.messages().is_empty());
2392 let recorded = run.pp.line_directives();
2393 assert_eq!(recorded.len(), 1);
2394 assert_eq!(recorded[0].line, 42);
2395 let file = recorded[0].file.expect("a file name was given");
2396 assert_eq!(run.interner.resolve(file), "\"other.c\"");
2397 }
2398
2399 #[test]
2400 fn line_moves_what_line_and_file_the_lines_after_it_are_on() {
2401 let mut run = Run::new();
2402 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2403 assert_eq!(run.go("#line 1000\n__LINE__ __FILE__\n__LINE__\n"), "1000 \"/main.c\" 1001");
2404 }
2405
2406 #[test]
2407 fn a_line_marker_moves_the_lines_after_it_the_way_line_does() {
2408 let mut run = Run::new();
2409 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2410 assert_eq!(run.go("# 200 \"xyz\"\n__FILE__ __LINE__\n"), "\"xyz\" 200");
2411 }
2412
2413 #[test]
2414 fn a_line_marker_with_no_name_leaves_the_name_alone() {
2415 let mut run = Run::new();
2416 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2417 assert_eq!(run.go("# 20\n__FILE__ __LINE__\n"), "\"/main.c\" 20");
2418 }
2419
2420 #[test]
2421 fn a_line_marker_may_say_line_zero() {
2422 let mut run = Run::new();
2425 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2426 assert_eq!(run.go("# 0 \"xyz\"\n__LINE__\n"), "0");
2427 }
2428
2429 #[test]
2430 fn entering_and_returning_are_a_nesting_the_marker_flags_keep() {
2431 let mut run = Run::new();
2432 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2433 let text =
2434 run.go("# 200 \"xyz\" 1\n__FILE__\n# 5 \"/main.c\" 2\n__FILE__ __LINE__\n# 9 3 4\n");
2435 assert_eq!(text, "\"xyz\" \"/main.c\" 5");
2436 assert!(run.messages().is_empty());
2437 }
2438
2439 #[test]
2440 fn returning_to_a_file_nothing_was_ever_in_is_ignored() {
2441 let mut run = Run::new();
2442 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2443 assert_eq!(run.go("# 200 \"xyz\" 2 3\n__FILE__ __LINE__\n"), "\"/main.c\" 2");
2444 assert_eq!(
2445 run.messages(),
2446 vec!["file `xyz` linemarker ignored due to incorrect nesting".to_owned()]
2447 );
2448 }
2449
2450 #[test]
2451 fn a_flag_that_is_not_one_of_the_four_is_an_error() {
2452 let mut run = Run::new();
2453 run.go("# 20 \"a\" 7\n");
2454 assert_eq!(run.messages(), vec!["invalid flag `7` in line directive".to_owned()]);
2455 }
2456
2457 #[test]
2458 fn a_hash_and_something_that_is_not_a_line_number_is_still_an_unknown_directive() {
2459 let mut run = Run::new();
2461 run.go("# 1.5 \"a\"\n");
2462 assert_eq!(run.messages(), vec!["invalid preprocessing directive".to_owned()]);
2463 }
2464
2465 #[test]
2466 fn a_name_on_the_directive_is_the_name_from_there_on() {
2467 let mut run = Run::new();
2468 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2469 assert_eq!(run.go("#line 7 \"gen.y\"\n__FILE__ __LINE__\n"), "\"gen.y\" 7");
2470 }
2471
2472 #[test]
2473 fn a_directive_with_no_name_keeps_the_one_already_in_force() {
2474 let mut run = Run::new();
2475 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2476 assert_eq!(run.go("#line 7 \"gen.y\"\n#line 20\n__FILE__ __LINE__\n"), "\"gen.y\" 20");
2477 }
2478
2479 #[test]
2480 fn the_number_is_expanded_first_because_line_plus_one_is_real_code() {
2481 let mut run = Run::new();
2482 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2483 assert_eq!(run.go("#define WHERE 300\n#line WHERE\n__LINE__\n"), "300");
2484 }
2485
2486 #[test]
2487 fn a_directive_in_a_header_does_not_move_the_file_that_included_it() {
2488 let mut run = Run::new();
2489 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2490 run.file("/h.h", "#line 500\n__LINE__\n");
2491 run.dir("/");
2492 assert_eq!(run.go("#include <h.h>\n__LINE__\n"), "500 2");
2493 }
2494
2495 #[test]
2496 fn extra_tokens_after_the_file_name_are_a_warning_and_not_an_error() {
2497 let mut run = Run::new();
2498 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2499 assert_eq!(run.go("#line 7 \"gen.y\" and more\n__LINE__\n"), "7");
2500 assert_eq!(run.messages(), vec!["extra tokens after `#line`".to_owned()]);
2501 }
2502
2503 #[test]
2504 fn a_line_number_out_of_range_is_refused() {
2505 let mut run = Run::new();
2506 run.go("#line 0\n");
2507 assert_eq!(run.messages(), vec!["`#line` number is out of range".to_owned()]);
2508
2509 let mut run = Run::new();
2510 run.go("#line notanumber\n");
2511 assert_eq!(run.messages(), vec!["`#line` needs a decimal line number".to_owned()]);
2512 }
2513
2514 #[test]
2515 fn a_pragma_passes_through_unchanged() {
2516 assert_eq!(clean("#pragma pack(1)\nint x;\n"), "#pragma pack(1) int x;");
2517 }
2518
2519 #[test]
2524 fn the_space_between_the_hash_and_the_word_comes_off_a_pragma_that_is_indented() {
2525 assert_eq!(clean("#if 1\n# pragma pack(1)\n#endif\n"), "#pragma pack(1)");
2526 assert_eq!(clean("# pragma pack( 1 )\n"), "#pragma pack( 1 )", "the rest is kept");
2527 }
2528
2529 #[test]
2530 fn the_pragma_operator_becomes_a_pragma() {
2531 assert_eq!(
2532 clean("_Pragma(\"GCC visibility push(default)\")\nint x;\n"),
2533 "#pragma GCC visibility push(default) int x;"
2534 );
2535 }
2536
2537 #[test]
2538 fn the_pragma_operator_works_from_inside_a_macro() {
2539 let src = "#define PUSH _Pragma(\"pack(push)\")\nPUSH\nint x;\n";
2542 assert_eq!(clean(src), "#pragma pack(push) int x;");
2543 }
2544
2545 #[test]
2549 fn what_follows_a_pragma_operator_starts_a_line() {
2550 let mut run = Run::new();
2551 let out = run.raw("int x; _Pragma(\"pack(1)\") int y;\n");
2552 let starts: Vec<_> =
2553 out.iter().map(|tok| tok.flags.has(TokenFlags::START_OF_LINE)).collect();
2554 assert_eq!(
2557 starts,
2558 vec![true, false, false, true, false, false, false, false, false, true, false, false]
2559 );
2560 }
2561
2562 #[test]
2567 fn a_macro_that_came_to_nothing_hands_on_the_line_it_started() {
2568 let mut run = Run::new();
2569 let out = run.raw("#define E\nint x;\nE int y;\n");
2570 let starts: Vec<_> =
2571 out.iter().map(|tok| tok.flags.has(TokenFlags::START_OF_LINE)).collect();
2572 assert_eq!(starts, vec![true, false, false, true, false, false]);
2574 }
2575
2576 #[test]
2578 fn a_run_of_macros_that_came_to_nothing_hands_the_line_along() {
2579 let mut run = Run::new();
2580 let out = run.raw("#define E\n#define F(x)\nE F(1) E int y;\n");
2581 let starts: Vec<_> =
2582 out.iter().map(|tok| tok.flags.has(TokenFlags::START_OF_LINE)).collect();
2583 assert_eq!(starts, vec![true, false, false]);
2584 }
2585
2586 #[test]
2587 fn a_pragma_operator_that_is_not_given_a_string_is_reported() {
2588 let mut run = Run::new();
2589 run.go("_Pragma(x)\n");
2590 assert_eq!(run.messages(), vec!["`_Pragma` takes a single string literal".to_owned()]);
2591 }
2592
2593 #[test]
2594 fn an_include_reads_the_file_it_names() {
2595 let mut run = Run::new();
2596 run.file("/dir/one.h", "int from_the_header;\n");
2597 run.dir("/dir");
2598 assert_eq!(run.go("#include <one.h>\nint after;\n"), "int from_the_header; int after;");
2599 assert!(run.messages().is_empty());
2600 }
2601
2602 #[test]
2603 fn a_quoted_include_looks_next_to_the_including_file_first() {
2604 let mut run = Run::new();
2605 run.file("/local.h", "beside\n");
2606 run.file("/dir/local.h", "on the path\n");
2607 run.dir("/dir");
2608 assert_eq!(run.go("#include \"local.h\"\n"), "beside");
2609 assert!(run.messages().is_empty());
2610 }
2611
2612 #[test]
2613 fn an_angled_include_does_not_look_next_to_the_including_file() {
2614 let mut run = Run::new();
2615 run.file("/local.h", "beside\n");
2616 run.file("/dir/local.h", "on the path\n");
2617 run.dir("/dir");
2618 assert_eq!(run.go("#include <local.h>\n"), "on the path");
2619 }
2620
2621 #[test]
2622 fn a_macro_defined_in_a_header_is_visible_after_the_include() {
2623 let mut run = Run::new();
2624 run.file("/dir/defs.h", "#define N 42\n");
2625 run.dir("/dir");
2626 assert_eq!(run.go("#include <defs.h>\nint a = N;\n"), "int a = 42;");
2627 assert!(run.messages().is_empty());
2628 }
2629
2630 #[test]
2631 fn an_include_guard_keeps_the_second_read_empty() {
2632 let mut run = Run::new();
2633 run.file("/dir/g.h", "#ifndef G\n#define G\nonce\n#endif\n");
2634 run.dir("/dir");
2635 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
2636 assert!(run.messages().is_empty());
2637 assert_eq!(run.files(), 2, "the second include is not opened at all");
2638 }
2639
2640 fn named(name: &str, macros_only: bool) -> Preinclude {
2641 Preinclude { name: name.to_owned(), macros_only }
2642 }
2643
2644 #[test]
2645 fn a_command_line_include_contributes_its_text_and_an_imacros_contributes_none() {
2646 let mut run = Run::new();
2647 run.file("i.h", "from_include\n#define I 1\n");
2648 run.file("m.h", "from_macros\n#define M 1\n");
2649 assert_eq!(run.preinclude(&[named("i.h", false), named("m.h", true)]), "from_include");
2650 assert_eq!(run.go("I M\n"), "1 1");
2652 }
2653
2654 #[test]
2655 fn every_imacros_runs_before_every_include_whatever_order_the_command_line_was_in() {
2656 for files in
2659 [[named("i.h", false), named("m.h", true)], [named("m.h", true), named("i.h", false)]]
2660 {
2661 let mut run = Run::new();
2662 run.file("i.h", "#ifdef M\nsaw_it\n#else\nmissed_it\n#endif\n");
2663 run.file("m.h", "#define M 1\n");
2664 assert_eq!(run.preinclude(&files), "saw_it");
2665 }
2666 }
2667
2668 #[test]
2669 fn a_header_read_for_its_macros_is_not_read_again_by_an_include_that_its_guard_covers() {
2670 let mut run = Run::new();
2673 run.file("/dir/g.h", "#ifndef G\n#define G\ndeclarations\n#endif\n");
2674 run.dir("/dir");
2675 assert_eq!(run.preinclude(&[named("/dir/g.h", true)]), "");
2676 assert_eq!(run.go("#include <g.h>\n"), "");
2677 assert!(run.messages().is_empty());
2678 }
2679
2680 #[test]
2681 fn a_command_line_include_is_a_dependency_and_is_named_before_the_headers_it_reads() {
2682 let mut run = Run::new();
2683 run.file("i.h", "#include \"deep.h\"\n");
2684 run.file("deep.h", "\n");
2685 run.file("m.h", "\n");
2686 run.preinclude(&[named("i.h", false), named("m.h", true)]);
2687 let names: Vec<String> =
2688 run.pp.dependencies().iter().map(|d| d.path.to_string_lossy().into_owned()).collect();
2689 let names: Vec<String> = names.iter().map(|n| n.replace('\\', "/")).collect();
2690 assert_eq!(names, ["m.h", "i.h", "deep.h"]);
2691 }
2692
2693 #[test]
2694 fn a_prerequisite_is_spelled_without_the_dot_the_search_path_was_written_with() {
2695 let mut run = Run::new();
2699 run.file("d/f.h", "\n");
2700 run.dir("./d");
2701 run.go("#include <f.h>\n");
2702 let names: Vec<String> =
2703 run.pp.dependencies().iter().map(|d| d.path.to_string_lossy().into_owned()).collect();
2704 assert_eq!(names.iter().map(|n| n.replace('\\', "/")).collect::<Vec<_>>(), ["d/f.h"]);
2705 }
2706
2707 #[test]
2708 fn a_command_line_include_that_is_nowhere_is_reported_against_the_flag_that_named_it() {
2709 let mut run = Run::new();
2710 assert_eq!(run.preinclude(&[named("nope.h", false)]), "");
2711 assert_eq!(run.messages(), ["`nope.h` file not found"]);
2712 }
2713
2714 #[test]
2715 fn the_other_spelling_of_a_guard_is_recognised_too() {
2716 for guard in ["#if !defined(G)", "#if !defined G"] {
2717 let mut run = Run::new();
2718 run.file("/dir/g.h", &format!("{guard}\n#define G\nonce\n#endif\n"));
2719 run.dir("/dir");
2720 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
2721 assert_eq!(run.files(), 2, "{guard} should be a guard");
2722 }
2723 }
2724
2725 #[test]
2726 fn a_conditional_that_is_not_a_guard_does_not_skip_anything() {
2727 let mut run = Run::new();
2730 run.file("/dir/g.h", "#ifndef G\ntwice\n#endif\n");
2731 run.dir("/dir");
2732 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "twice twice");
2733 assert_eq!(run.files(), 3);
2734 }
2735
2736 #[test]
2737 fn a_file_whose_opening_conditional_has_another_branch_is_read_again() {
2738 let mut run = Run::new();
2745 let file = "#ifndef G\n#define G\nfirst\n#elif !defined H\n#define H\nsecond\n#else\nthird\n#endif\n";
2746 run.file("/dir/g.h", file);
2747 run.dir("/dir");
2748 let out = run.go("#include <g.h>\n#include <g.h>\n#include <g.h>\n");
2749 assert_eq!(out, "first second third");
2750 assert_eq!(run.files(), 4, "the file is opened once for each include");
2751 }
2752
2753 #[test]
2754 fn a_branch_inside_the_guard_is_not_the_guard_having_a_branch() {
2755 let mut run = Run::new();
2758 let file = "#ifndef G\n#define G\n#if 0\nno\n#else\nonce\n#endif\n#endif\n";
2759 run.file("/dir/g.h", file);
2760 run.dir("/dir");
2761 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
2762 assert_eq!(run.files(), 2, "the second include is skipped");
2763 }
2764
2765 #[test]
2766 fn a_token_outside_the_guard_stops_it_being_a_guard() {
2767 let mut run = Run::new();
2768 run.file("/dir/g.h", "#ifndef G\n#define G\n#endif\nalways\n");
2769 run.dir("/dir");
2770 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "always always");
2771 assert_eq!(run.files(), 3);
2772 }
2773
2774 #[test]
2775 fn pragma_once_skips_the_second_read_and_does_not_reach_the_output() {
2776 let mut run = Run::new();
2777 run.file("/dir/o.h", "#pragma once\nonce\n");
2778 run.dir("/dir");
2779 assert_eq!(run.go("#include <o.h>\n#include <o.h>\n"), "once");
2780 assert!(run.messages().is_empty());
2781 assert_eq!(run.files(), 2);
2782 }
2783
2784 #[test]
2785 fn pragma_once_in_the_main_file_is_a_warning_and_is_still_applied() {
2786 let mut run = Run::new();
2790 let src = "#pragma once\n#include <s.c>\nbody\n";
2791 run.file("/dir/s.c", src);
2792 run.dir("/dir");
2793 assert_eq!(run.go_named("/dir/s.c", src), "body");
2794 assert_eq!(run.severities(), vec![Severity::Warning]);
2795 assert_eq!(run.messages(), vec!["`#pragma once` in the main file".to_owned()]);
2796 assert_eq!(run.files(), 1);
2797 }
2798
2799 #[test]
2800 fn pragma_once_holds_across_two_spellings_of_the_one_path() {
2801 let mut run = Run::new();
2804 run.file("dir/s.c", "#pragma once\nbody\n");
2805 run.dir(".");
2806 assert_eq!(run.go("#include <dir/s.c>\n#include <dir/s.c>\n"), "body");
2807 assert!(run.messages().is_empty());
2808 assert_eq!(run.files(), 2);
2809 }
2810
2811 #[test]
2812 fn any_other_pragma_still_passes_through() {
2813 assert_eq!(clean("#pragma once_upon_a_time\n"), "#pragma once_upon_a_time");
2814 }
2815
2816 #[test]
2819 fn push_macro_and_pop_macro_put_a_definition_aside_and_bring_it_back() {
2820 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";
2821 assert_eq!(clean(src), "a 2 b 1");
2822 }
2823
2824 #[test]
2825 fn a_name_with_no_definition_pushes_and_pops_the_absence() {
2826 let src = "#pragma push_macro(\"X\")\n#define X 1\na X\n#pragma pop_macro(\"X\")\nb X\n";
2829 assert_eq!(clean(src), "a 1 b X");
2830 }
2831
2832 #[test]
2833 fn the_pushes_nest() {
2834 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";
2835 assert_eq!(clean(src), "a 3 b 2 c 1");
2836 }
2837
2838 #[test]
2839 fn a_pop_with_nothing_pushed_says_nothing() {
2840 assert_eq!(clean("#define X 1\n#pragma pop_macro(\"X\")\nX\n"), "1");
2843 assert_eq!(clean("#pragma pop_macro(\"Never\")\nx\n"), "x");
2844 }
2845
2846 #[test]
2847 fn the_pragma_operator_spelling_works_and_takes_effect_where_it_is_written() {
2848 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";
2853 assert_eq!(clean(src), "a 2 b 1");
2854 }
2855
2856 #[test]
2857 fn the_gcc_spelling_is_not_one_of_these_and_passes_through() {
2858 let src = "#define X 1\n#pragma GCC push_macro(\"X\")\n#undef X\n#define X 2\nX\n";
2862 assert_eq!(clean(src), "#pragma GCC push_macro(\"X\") 2");
2863 }
2864
2865 #[test]
2866 fn a_push_macro_that_is_not_the_shape_is_an_error() {
2867 for src in ["#pragma push_macro\n", "#pragma push_macro(X)\n", "#pragma pop_macro()\n"] {
2868 let mut run = Run::new();
2869 run.go(src);
2870 let word = if src.contains("push") { "push" } else { "pop" };
2871 assert_eq!(
2872 run.messages(),
2873 vec![format!("invalid `#pragma {word}_macro` directive")],
2874 "from {src:?}"
2875 );
2876 }
2877 }
2878
2879 #[test]
2880 fn a_string_that_does_not_spell_one_identifier_names_no_macro() {
2881 assert_eq!(clean("#pragma push_macro(\"a b\")\nx\n"), "x");
2884 assert_eq!(clean("#pragma push_macro(\"2\")\nx\n"), "x");
2885 }
2886
2887 #[test]
2888 fn what_follows_the_closing_parenthesis_is_the_usual_warning() {
2889 let mut run = Run::new();
2890 assert_eq!(run.go("#define X 1\n#pragma push_macro(\"X\") junk\nX\n"), "1");
2891 assert_eq!(run.severities(), vec![Severity::Warning]);
2892 assert_eq!(run.messages(), vec!["extra tokens after `#pragma`".to_owned()]);
2893 }
2894
2895 #[test]
2896 fn has_include_answers_from_the_search_path() {
2897 let mut run = Run::new();
2898 run.file("/dir/there.h", "");
2899 run.dir("/dir");
2900 let src = "#if __has_include(<there.h>)\nyes\n#endif\n\
2901 #if __has_include(<gone.h>)\nno\n#endif\n";
2902 assert_eq!(run.go(src), "yes");
2903 assert!(run.messages().is_empty(), "a header that is not there is an answer, not an error");
2904 }
2905
2906 #[test]
2907 fn has_include_asks_the_question_the_include_on_the_same_line_would() {
2908 let mut run = Run::new();
2912 run.file("/beside.h", "");
2913 let src = "#if __has_include(\"beside.h\")\nquoted\n#endif\n\
2914 #if __has_include(<beside.h>)\nangled\n#endif\n";
2915 assert_eq!(run.go(src), "quoted");
2916 }
2917
2918 #[test]
2919 fn has_include_next_starts_where_include_next_would() {
2920 let mut run = Run::new();
2921 run.file("/a/both.h", "#if __has_include_next(<both.h>)\nmore\n#endif\n");
2922 run.file("/b/both.h", "last\n");
2923 run.file("/a/only.h", "#if __has_include_next(<only.h>)\nmore\n#endif\n");
2924 run.dir("/a");
2925 run.dir("/b");
2926 assert_eq!(run.go("#include <both.h>\n"), "more");
2927 assert_eq!(run.go("#include <only.h>\n"), "", "there is nothing after /a to find it in");
2928 }
2929
2930 #[test]
2931 fn the_operand_of_has_include_is_not_macro_expanded() {
2932 let mut run = Run::new();
2935 run.file("/dir/linux/version.h", "");
2936 run.dir("/dir");
2937 let src = "#define linux 1\n#if __has_include(<linux/version.h>)\nyes\n#endif\n";
2938 assert_eq!(run.go(src), "yes");
2939 }
2940
2941 #[test]
2942 fn a_macro_may_expand_to_a_has_include() {
2943 let mut run = Run::new();
2945 run.file("/dir/there.h", "");
2946 run.dir("/dir");
2947 let src = "#define HAVE __has_include(<there.h>)\n#if HAVE\nyes\n#endif\n";
2948 assert_eq!(run.go(src), "yes");
2949 }
2950
2951 #[test]
2952 fn defined_says_the_has_operators_are_there() {
2953 let src = "#if defined(__has_include) && defined __has_builtin\nyes\n#endif\n";
2956 assert_eq!(clean(src), "yes");
2957 assert_eq!(clean("#ifdef __has_attribute\nyes\n#endif\n"), "yes");
2958 }
2959
2960 #[test]
2961 fn has_attribute_answers_out_of_the_matrix() {
2962 assert_eq!(clean("#if __has_attribute(packed)\nyes\n#endif\n"), "yes");
2966 assert_eq!(clean("#if __has_attribute(flatten)\nyes\n#endif\n"), "");
2967 assert_eq!(clean("#if __has_attribute(no_such_attribute)\nyes\n#endif\n"), "");
2968 assert_eq!(clean("#if !__has_attribute(flatten)\nno\n#endif\n"), "no");
2969 }
2970
2971 #[test]
2972 fn the_scoped_spelling_of_an_attribute_is_the_same_question() {
2973 assert_eq!(clean("#if __has_c_attribute(gnu::packed)\nyes\n#endif\n"), "");
2979 assert_eq!(clean("#if __has_c_attribute(deprecated)\nyes\n#endif\n"), "");
2980 }
2981
2982 #[test]
2983 fn has_builtin_answers_no_until_the_builtin_is_real() {
2984 assert_eq!(clean("#if __has_builtin(__builtin_expect)\nyes\n#endif\n"), "yes");
2985 assert_eq!(clean("#if __has_builtin(__builtin_clz)\nyes\n#endif\n"), "yes");
2986 assert_eq!(clean("#if __has_builtin(__builtin_alloca)\nyes\n#endif\n"), "yes");
2987 assert_eq!(clean("#if __has_builtin(__builtin_object_size)\nyes\n#endif\n"), "yes");
2988 assert_eq!(clean("#if __has_builtin(__atomic_signal_fence)\nyes\n#endif\n"), "");
2989 assert_eq!(clean("#if __has_builtin(__builtin_nonesuch)\nyes\n#endif\n"), "");
2990 }
2991
2992 #[test]
2993 fn has_feature_and_has_extension_read_the_same_table() {
2994 assert_eq!(clean("#if __has_feature(pragma_once)\nyes\n#endif\n"), "yes");
2997 assert_eq!(clean("#if __has_extension(pragma_once)\nyes\n#endif\n"), "yes");
2998 assert_eq!(clean("#if __has_extension(include_next)\nyes\n#endif\n"), "yes");
2999 assert_eq!(clean("#if __has_feature(include_next)\nyes\n#endif\n"), "");
3000 assert_eq!(clean("#if __has_feature(statement_expressions)\nyes\n#endif\n"), "");
3001 }
3002
3003 #[test]
3004 fn building_module_is_always_no_and_is_recognised_so_that_the_line_parses() {
3005 assert_eq!(clean("#if __building_module(m)\nyes\n#endif\n"), "");
3009 assert_eq!(clean("#if !__building_module(m)\nyes\n#endif\n"), "yes");
3010 assert_eq!(
3011 clean(
3012 "#if !defined(offsetof) || (__has_feature(modules) && !__building_module(x))\nyes\n#endif\n"
3013 ),
3014 "yes"
3015 );
3016 assert_eq!(clean("#ifdef __building_module\nyes\n#endif\n"), "yes");
3018 assert_eq!(clean("#if defined(__building_module)\nyes\n#endif\n"), "yes");
3019 }
3020
3021 #[test]
3022 fn a_has_operator_without_an_operand_is_reported() {
3023 let mut run = Run::new();
3024 run.go("#if __has_include\nyes\n#endif\n");
3025 assert_eq!(run.messages(), ["expected `(` after `__has_include`"]);
3026 let mut run = Run::new();
3027 run.go("#if __has_include(1)\nyes\n#endif\n");
3028 assert_eq!(run.messages(), ["expected a file name in `<>` or `\"\"`"]);
3029 let mut run = Run::new();
3030 run.go("#if __has_attribute(\"packed\")\nyes\n#endif\n");
3031 assert_eq!(run.messages(), ["expected an identifier as the operand of `__has_attribute`"]);
3032 }
3033
3034 #[test]
3035 fn the_has_operators_answer_in_ordinary_text_too() {
3036 assert_eq!(clean("f __has_feature(pragma_once)\n"), "f 1");
3040 assert_eq!(clean("b __has_builtin(__builtin_expect)\n"), "b 1");
3041 assert_eq!(clean("a __has_attribute(packed)\n"), "a 1");
3042 assert_eq!(clean("c __has_c_attribute(deprecated)\n"), "c 0");
3043 assert_eq!(clean("m __building_module(foo)\n"), "m 0");
3044 }
3045
3046 #[test]
3047 fn a_macro_that_expands_to_a_has_operator_is_answered_where_it_is_used() {
3048 assert_eq!(clean("#define HAVE __has_feature(pragma_once)\nx HAVE\n"), "x 1");
3051 assert_eq!(clean("#define HAVE(x) __has_attribute(x)\ny HAVE(packed)\n"), "y 1");
3052 }
3053
3054 #[test]
3055 fn a_has_operator_in_text_still_needs_its_operand() {
3056 let mut run = Run::new();
3057 run.go("tail __has_attribute;\n");
3058 assert_eq!(run.messages(), ["expected `(` after `__has_attribute`"]);
3059 }
3060
3061 #[test]
3062 fn the_header_operators_are_refused_in_ordinary_text() {
3063 let mut run = Run::new();
3066 run.file("/dir/there.h", "");
3067 run.dir("/dir");
3068 run.go("a __has_include(<there.h>)\n");
3069 assert_eq!(run.messages(), ["`__has_include` used outside of a preprocessing directive"]);
3070 let mut run = Run::new();
3071 run.go("b __has_include_next(\"x.h\")\n");
3072 assert_eq!(
3073 run.messages(),
3074 ["`__has_include_next` used outside of a preprocessing directive"]
3075 );
3076 }
3077
3078 #[test]
3079 fn the_predefined_set_is_visible_to_the_source_file() {
3080 let mut run = Run::new();
3081 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3082 let src = "#if defined(__x86_64__) && defined(__linux__) && __SIZEOF_LONG__ == 8\n\
3083 yes\n#endif\n";
3084 assert_eq!(run.go(src), "yes");
3085 assert!(run.messages().is_empty());
3086 }
3087
3088 #[test]
3089 fn the_predefined_set_follows_the_target_and_not_the_host() {
3090 let mut run = Run::new();
3091 run.predefine("aarch64-unknown-linux-gnu", &Predef::new());
3092 assert_eq!(
3093 run.go("#ifdef __x86_64__\nno\n#endif\n#ifdef __aarch64__\nyes\n#endif\n"),
3094 "yes"
3095 );
3096 }
3097
3098 #[test]
3099 fn a_predefined_macro_expands_where_it_is_used() {
3100 let mut run = Run::new();
3101 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3102 assert_eq!(run.go("__SIZE_TYPE__ n;\n"), "long unsigned int n;");
3103 }
3104
3105 #[test]
3106 fn a_command_line_define_is_a_definition_like_any_other() {
3107 let mut opts = Predef::new();
3108 opts.defines = vec!["FOO".to_owned(), "BAR=3".to_owned()];
3109 opts.undefines = vec!["__linux__".to_owned()];
3110 let mut run = Run::new();
3111 run.predefine("x86_64-unknown-linux-gnu", &opts);
3112 let src = "#if FOO && BAR == 3 && !defined(__linux__)\nyes\n#endif\n";
3113 assert_eq!(run.go(src), "yes");
3114 assert!(run.messages().is_empty());
3115 }
3116
3117 #[test]
3118 fn the_predefined_set_produces_no_tokens_of_its_own() {
3119 let mut run = Run::new();
3122 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3123 assert_eq!(run.go("alone\n"), "alone");
3124 }
3125
3126 #[test]
3127 fn the_predefined_files_are_named_the_way_gcc_names_them() {
3128 let mut run = Run::new();
3129 let mut opts = Predef::new();
3130 opts.defines = vec!["FOO=1".to_owned()];
3131 run.predefine("x86_64-unknown-linux-gnu", &opts);
3132 let names: Vec<&str> = run.sources.files().iter().map(|f| f.name.as_str()).collect();
3133 assert_eq!(names, ["<built-in>", "<command-line>"]);
3134 }
3135
3136 #[test]
3137 fn a_dialect_without_the_gnu_extensions_says_so() {
3138 let mut opts = Predef::new();
3139 opts.gnu_extensions = false;
3140 opts.std = Std::C99;
3141 let mut run = Run::new();
3142 run.predefine("x86_64-unknown-linux-gnu", &opts);
3143 let src = "#if defined(__STRICT_ANSI__) && __STDC_VERSION__ == 199901L && !defined(linux)\n\
3144 yes\n#endif\n";
3145 assert_eq!(run.go(src), "yes");
3146 }
3147
3148 #[test]
3149 fn the_date_and_time_are_the_same_for_the_whole_translation_unit() {
3150 let mut opts = Predef::new();
3151 opts.timestamp = Timestamp::from_unix(0);
3152 let mut run = Run::new();
3153 run.predefine("x86_64-unknown-linux-gnu", &opts);
3154 assert_eq!(run.go("__DATE__ __TIME__\n"), "\"Jan 1 1970\" \"00:00:00\"");
3155 }
3156
3157 #[test]
3158 fn a_has_operator_in_a_dead_branch_is_not_asked_about() {
3159 assert_eq!(clean("#if 0\n#if __has_include\n#endif\n#endif\nafter\n"), "after");
3161 }
3162
3163 #[test]
3164 fn a_conditional_may_not_span_an_include() {
3165 let mut run = Run::new();
3169 run.file("/dir/open.h", "#if 1\n");
3170 run.dir("/dir");
3171 run.go("#include <open.h>\nkept\n#endif\n");
3172 let messages = run.messages();
3173 assert_eq!(messages.len(), 2);
3174 assert!(messages[0].contains("unterminated"));
3175 assert!(messages[1].contains("without"));
3176 }
3177
3178 #[test]
3179 fn include_next_continues_after_the_directory_the_file_came_from() {
3180 let mut run = Run::new();
3183 run.file("/a/limits.h", "wrapper\n#include_next <limits.h>\n");
3184 run.file("/b/limits.h", "real\n");
3185 run.dir("/a");
3186 run.dir("/b");
3187 assert_eq!(run.go("#include <limits.h>\n"), "wrapper real");
3188 assert!(run.messages().is_empty());
3189 }
3190
3191 #[test]
3192 fn a_computed_include_is_expanded_first() {
3193 let mut run = Run::new();
3194 run.file("/dir/sub/thing.h", "computed\n");
3195 run.dir("/dir");
3196 let src = "#define HEADER <sub/thing.h>\n#include HEADER\n";
3197 assert_eq!(run.go(src), "computed");
3198 assert!(run.messages().is_empty());
3199 let mut run = Run::new();
3201 run.file("/dir/sub/thing.h", "computed\n");
3202 run.dir("/dir");
3203 assert_eq!(run.go("#define H \"sub/thing.h\"\n#include H\n"), "computed");
3204 }
3205
3206 #[test]
3207 fn a_header_that_is_not_there_says_where_it_looked() {
3208 let mut run = Run::new();
3209 run.dir("/dir");
3210 run.go("#include <nope.h>\n");
3211 let diagnostics = run.pp.take_diagnostics();
3212 assert_eq!(diagnostics.len(), 1);
3213 assert_eq!(diagnostics[0].code, Some("E0341"));
3214 assert_eq!(diagnostics[0].message, "`nope.h` file not found");
3215 assert!(diagnostics[0].children[0].message.contains("/dir"));
3216 }
3217
3218 #[test]
3225 fn a_header_that_is_not_there_says_why_the_system_directories_are_missing() {
3226 let mut run = Run::new();
3227 run.search.explain_missing_system("aarch64-macos needs a macOS SDK and there is none here");
3228 run.go("#include <stdio.h>\n");
3229 let diagnostics = run.pp.take_diagnostics();
3230 assert_eq!(diagnostics.len(), 1);
3231 assert_eq!(diagnostics[0].message, "`stdio.h` file not found");
3232 assert!(diagnostics[0].children[0].message.contains("search path is empty"));
3233 assert!(diagnostics[0].children[1].message.contains("needs a macOS SDK"));
3234 let mut run = Run::new();
3236 run.dir("/dir");
3237 run.go("#include <nope.h>\n");
3238 assert_eq!(run.pp.take_diagnostics()[0].children.len(), 1);
3239 }
3240
3241 #[test]
3242 fn an_include_that_is_not_a_header_name_is_reported() {
3243 let mut run = Run::new();
3244 run.go("#include 3\n");
3245 let diagnostics = run.pp.take_diagnostics();
3246 assert_eq!(diagnostics[0].code, Some("E0343"));
3247 }
3248
3249 #[test]
3250 fn a_header_that_includes_itself_stops() {
3251 let mut run = Run::new();
3252 run.file("/dir/loop.h", "#include <loop.h>\n");
3253 run.dir("/dir");
3254 run.go("#include <loop.h>\n");
3255 let diagnostics = run.pp.take_diagnostics();
3256 assert_eq!(diagnostics.len(), 1, "one complaint, not one per level");
3257 assert_eq!(diagnostics[0].code, Some("E0342"));
3258 }
3259
3260 #[test]
3261 fn an_include_in_a_dead_branch_is_not_read() {
3262 let mut run = Run::new();
3263 assert_eq!(run.go("#if 0\n#include <nothing.h>\n#endif\nafter\n"), "after");
3264 assert!(run.messages().is_empty(), "a skipped include is not resolved");
3265 }
3266
3267 #[test]
3268 fn embed_writes_the_bytes_of_the_resource() {
3269 let mut run = Run::new();
3270 run.bytes("/logo.bin", &[0, 1, 127, 128, 255]);
3271 assert_eq!(run.go("#embed \"logo.bin\"\n"), "0, 1, 127, 128, 255");
3272 assert!(run.messages().is_empty());
3273 }
3274
3275 #[test]
3276 fn an_embed_is_a_valid_initializer_on_both_sides_of_empty() {
3277 let mut run = Run::new();
3282 run.bytes("/some.bin", &[7, 8]);
3283 run.bytes("/none.bin", &[]);
3284 let line = |name: &str| {
3285 format!("{{\n#embed \"{name}\" prefix(0xEF,) suffix(,0xFE) if_empty(0)\n}}\n")
3286 };
3287 assert_eq!(run.go(&line("some.bin")), "{ 0xEF,7, 8 ,0xFE }");
3288 assert_eq!(run.go_named("/other.c", &line("none.bin")), "{ 0 }");
3289 assert!(run.messages().is_empty());
3290 }
3291
3292 #[test]
3293 fn the_limit_and_the_offset_choose_a_window_of_the_resource() {
3294 let mut run = Run::new();
3295 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
3296 assert_eq!(run.go("#embed \"eight.bin\" limit(3)\n"), "1, 2, 3");
3297 assert_eq!(
3298 run.go_named("/b.c", "#embed \"eight.bin\" gnu::offset(4) limit(3)\n"),
3299 "5, 6, 7"
3300 );
3301 assert_eq!(run.go_named("/c.c", "#embed \"eight.bin\" limit(0) if_empty(9)\n"), "9");
3304 assert_eq!(run.go_named("/d.c", "#embed \"eight.bin\" gnu::offset(99)\n"), "");
3305 assert!(run.messages().is_empty());
3306 }
3307
3308 #[test]
3309 fn the_limit_is_a_constant_expression_and_not_just_a_number() {
3310 let mut run = Run::new();
3313 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
3314 assert_eq!(
3315 run.go("#define CHUNK 2\n#embed \"eight.bin\" limit(CHUNK * 2)\n"),
3316 "1, 2, 3, 4"
3317 );
3318 assert!(run.messages().is_empty());
3319 }
3320
3321 #[test]
3322 fn a_misspelled_embed_parameter_is_refused_rather_than_ignored() {
3323 let mut run = Run::new();
3326 run.bytes("/eight.bin", &[1, 2]);
3327 assert_eq!(run.go("#embed \"eight.bin\" limits(1)\n"), "");
3328 assert_eq!(run.messages(), vec!["unknown `#embed` parameter `limits`".to_owned()]);
3329 let mut vendor = Run::new();
3330 vendor.bytes("/eight.bin", &[1, 2]);
3331 assert_eq!(vendor.go("#embed \"eight.bin\" clang::offset(1)\n"), "");
3332 assert_eq!(
3333 vendor.messages(),
3334 vec!["unknown `#embed` parameter `clang::offset`".to_owned()]
3335 );
3336 }
3337
3338 #[test]
3339 fn a_missing_embed_resource_is_reported_as_a_resource() {
3340 let mut run = Run::new();
3341 run.go("#embed <nothing.bin>\n");
3342 assert_eq!(run.messages(), vec!["`nothing.bin` resource not found".to_owned()]);
3343 }
3344
3345 #[test]
3346 fn has_embed_tells_missing_from_present_from_empty() {
3347 let mut run = Run::new();
3351 run.bytes("/some.bin", &[1]);
3352 run.bytes("/none.bin", &[]);
3353 let src = "#if __has_embed(\"none.bin\") == __STDC_EMBED_EMPTY__\nempty\n#endif\n\
3354 #if __has_embed(\"some.bin\") == __STDC_EMBED_FOUND__\nfound\n#endif\n\
3355 #if __has_embed(\"gone.bin\") == __STDC_EMBED_NOT_FOUND__\ngone\n#endif\n";
3356 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
3357 assert_eq!(run.go(src), "empty found gone");
3358 assert!(run.messages().is_empty());
3359 }
3360
3361 #[test]
3362 fn has_embed_takes_the_limit_into_account() {
3363 let mut run = Run::new();
3366 run.bytes("/some.bin", &[1, 2, 3]);
3367 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
3368 let src = "#if __has_embed(\"some.bin\" limit(0)) == __STDC_EMBED_EMPTY__\nempty\n#endif\n";
3369 assert_eq!(run.go(src), "empty");
3370 assert!(run.messages().is_empty());
3371 }
3372
3373 #[test]
3374 fn a_directive_may_have_space_before_the_hash_and_after_it() {
3375 assert_eq!(clean(" # define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
3376 }
3377
3378 #[test]
3379 fn a_definition_survives_across_a_conditional() {
3380 assert_eq!(clean("#if 1\n#define F 7\n#endif\nF\n"), "7");
3381 }
3382
3383 #[test]
3384 fn an_empty_if_expression_is_reported() {
3385 let mut run = Run::new();
3386 run.go("#if\n#endif\n");
3387 assert_eq!(run.messages(), vec!["`#if` with no expression".to_owned()]);
3388 }
3389
3390 #[test]
3391 fn the_file_and_the_line_say_where_the_use_is() {
3392 let mut run = Run::new();
3393 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3394 assert_eq!(run.go("__FILE__ __LINE__\n__LINE__\n"), "\"/main.c\" 1 2");
3395 assert!(run.messages().is_empty());
3396 }
3397
3398 #[test]
3399 fn a_macro_that_mentions_the_line_answers_with_the_call() {
3400 let mut run = Run::new();
3401 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3402 run.file("/where.h", "#define WHERE __FILE__ __LINE__\n");
3403 assert_eq!(run.go("#include \"where.h\"\n\n\nWHERE\n"), "\"/main.c\" 4");
3407 assert!(run.messages().is_empty());
3408 }
3409
3410 #[test]
3411 fn the_file_name_is_the_file_without_the_directories() {
3412 let mut run = Run::new();
3413 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3414 assert_eq!(run.go_named("/deep/down/main.c", "__FILE_NAME__\n"), "\"main.c\"");
3415 }
3416
3417 #[test]
3418 fn a_backslash_in_the_name_is_escaped() {
3419 let mut run = Run::new();
3420 run.predefine("x86_64-pc-windows-msvc", &Predef::new());
3421 let text = run.go_named("C:\\src\\main.c", "__FILE__ __FILE_NAME__\n");
3424 assert_eq!(text, "\"C:\\\\src\\\\main.c\" \"main.c\"");
3425 }
3426
3427 #[test]
3428 fn the_base_file_is_the_one_named_on_the_command_line() {
3429 let mut run = Run::new();
3430 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3431 run.file("/deep.h", "__FILE__ __BASE_FILE__\n");
3432 assert_eq!(run.go("#include \"deep.h\"\n"), "\"/deep.h\" \"/main.c\"");
3433 assert!(run.messages().is_empty());
3434 }
3435
3436 #[test]
3437 fn a_prefix_map_rewrites_the_file_and_the_base_file_and_not_the_file_name() {
3438 let mut run = Run::mapping(&[("/build", ".")]);
3439 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3440 run.file("/build/deep.h", "__FILE__ __BASE_FILE__ __FILE_NAME__\n");
3441 let text = run.go_named("/build/main.c", "#include \"deep.h\"\n");
3447 assert_eq!(slashes(&text), "\"./deep.h\" \"./main.c\" \"deep.h\"");
3450 assert!(run.messages().is_empty());
3451 }
3452
3453 #[test]
3454 fn the_last_rewrite_that_matches_is_the_one_that_acts() {
3455 let mut run = Run::mapping(&[("/build", "src"), ("/build/gen", "generated")]);
3458 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3459 assert_eq!(run.go_named("/build/gen/made.c", "__FILE__\n"), "\"generated/made.c\"");
3460
3461 let mut run = Run::mapping(&[("/build", "src"), ("/build/gen", "generated")]);
3462 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3463 assert_eq!(run.go_named("/build/hand.c", "__FILE__\n"), "\"src/hand.c\"");
3464
3465 let mut run = Run::mapping(&[("/build", "src")]);
3468 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3469 assert_eq!(run.go_named("/elsewhere/main.c", "__FILE__\n"), "\"/elsewhere/main.c\"");
3470 }
3471
3472 #[test]
3473 fn a_rewrite_matches_the_characters_and_not_the_directories() {
3474 let mut run = Run::mapping(&[("/bui", "X")]);
3480 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3481 assert_eq!(run.go_named("/build/main.c", "__FILE__\n"), "\"Xld/main.c\"");
3482 }
3483
3484 #[test]
3485 fn the_include_level_counts_the_headers_above_it() {
3486 let mut run = Run::new();
3487 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3488 run.file("/one.h", "__INCLUDE_LEVEL__\n#include \"two.h\"\n");
3489 run.file("/two.h", "__INCLUDE_LEVEL__\n");
3490 assert_eq!(run.go("__INCLUDE_LEVEL__\n#include \"one.h\"\n"), "0 1 2");
3491 assert!(run.messages().is_empty());
3492 }
3493
3494 #[test]
3495 fn the_counter_is_a_different_number_every_time() {
3496 let mut run = Run::new();
3497 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3498 assert_eq!(run.go("__COUNTER__ __COUNTER__ __COUNTER__\n"), "0 1 2");
3499 }
3500
3501 #[test]
3502 fn the_counter_advances_once_per_argument_rather_than_once_per_use() {
3503 let mut run = Run::new();
3504 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3505 assert_eq!(run.go("#define TWICE(x) x x\nTWICE(__COUNTER__) __COUNTER__\n"), "0 0 1");
3509 }
3510
3511 #[test]
3512 fn the_line_is_a_number_an_if_can_use() {
3513 let mut run = Run::new();
3514 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3515 assert_eq!(run.go("#if __LINE__ == 1 && __INCLUDE_LEVEL__ == 0\nyes\n#endif\n"), "yes");
3516 assert!(run.messages().is_empty());
3517 }
3518
3519 #[test]
3520 fn the_dynamic_macros_are_defined_like_any_others() {
3521 let mut run = Run::new();
3522 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3523 let src = "#ifdef __FILE__\nyes\n#endif\n#undef __LINE__\n#ifndef __LINE__\ngone\n#endif\n";
3524 assert_eq!(run.go(src), "yes gone");
3525 assert!(run.messages().is_empty(), "`#undef` of a builtin is allowed, as it is in GCC");
3526 }
3527
3528 #[test]
3529 fn redefining_a_dynamic_macro_warns_and_points_at_the_built_in_file() {
3530 let mut run = Run::new();
3531 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3532 assert_eq!(run.go("#define __FILE__ \"mine.c\"\n__FILE__\n"), "\"mine.c\"");
3533 let complaints = run.pp.take_diagnostics();
3534 assert_eq!(complaints.len(), 1);
3535 assert_eq!(complaints[0].code, Some("W0301"));
3536 let previous = complaints[0].children.first().expect("a note saying where it was");
3537 assert_eq!(run.sources.lookup(previous.span.lo).map(|loc| loc.file), {
3538 let built_in = run.sources.files().iter().find(|f| f.name == BUILT_IN);
3539 built_in.map(|f| f.id)
3540 });
3541 }
3542
3543 #[test]
3544 fn destringizing_undoes_what_stringizing_did() {
3545 assert_eq!(destringize(r#""a \"b\" c""#), r#"a "b" c"#);
3546 assert_eq!(destringize(r#""a \\ b""#), r"a \ b");
3547 assert_eq!(destringize(r#"L"wide""#), "wide");
3548 }
3549}