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, Preinclude};
25use rucc_target::TargetInfo;
26
27use crate::cond;
28use crate::embed;
29use crate::expand::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 macros(&self) -> &MacroTable {
150 &self.macros
151 }
152
153 pub fn macros_mut(&mut self) -> &mut MacroTable {
155 &mut self.macros
156 }
157
158 pub fn diagnostics(&self) -> &[Diagnostic] {
160 &self.diagnostics
161 }
162
163 pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
165 std::mem::take(&mut self.diagnostics)
166 }
167
168 pub fn dependencies(&self) -> &[Dependency] {
173 &self.deps
174 }
175
176 pub fn line_directives(&self) -> &[LineDirective] {
182 &self.lines
183 }
184
185 pub fn predefine(
197 &mut self,
198 target: &TargetInfo,
199 opts: &Predef,
200 cx: &mut Context<'_>,
201 ) -> Result<(), SourceMapFull> {
202 let names = Names::new(cx.interner);
203 let file = self.synthetic(BUILT_IN, built_in(target, opts), cx, &names)?;
204 let start = cx.sources.file(file).start;
210 for (spelling, builtin) in Builtin::ALL {
211 let name = cx.interner.intern(spelling);
212 self.macros.define_builtin(name, builtin, Span::new(start, start));
213 }
214 let text = command_line(opts);
215 if !text.is_empty() {
216 self.synthetic(COMMAND_LINE, text, cx, &names)?;
217 }
218 Ok(())
219 }
220
221 fn synthetic(
223 &mut self,
224 name: &str,
225 text: String,
226 cx: &mut Context<'_>,
227 names: &Names,
228 ) -> Result<FileId, SourceMapFull> {
229 let file = cx.sources.add(name, text.into_bytes())?;
230 let mut out = Vec::new();
231 let path = PathBuf::from(name);
235 let id = cx.fs.identity(&path);
236 self.stack.push(Frame { at: Span::DUMMY, path, id, dir: None, next: 0 });
237 self.process(file, &mut out, cx, names);
238 self.stack.clear();
239 debug_assert!(out.is_empty(), "{name} is directives only and produces no tokens");
240 Ok(file)
241 }
242
243 pub fn preinclude(
265 &mut self,
266 files: &[Preinclude],
267 out: &mut Vec<Tok>,
268 cx: &mut Context<'_>,
269 ) -> Result<(), SourceMapFull> {
270 if files.is_empty() {
271 return Ok(());
272 }
273 let names = Names::new(cx.interner);
274 let mut text = String::new();
278 let mut order: Vec<(usize, &Preinclude)> = Vec::new();
279 for macros_only in [true, false] {
280 for file in files.iter().filter(|f| f.macros_only == macros_only) {
281 text.push_str(if macros_only { "-imacros " } else { "-include " });
282 order.push((text.len(), file));
283 text.push_str(&file.name);
284 text.push('\n');
285 }
286 }
287 let record = cx.sources.add(COMMAND_LINE, text.into_bytes())?;
288 let start = cx.sources.file(record).start;
289 let path = PathBuf::from(COMMAND_LINE);
293 let id = cx.fs.identity(&path);
294 self.stack.push(Frame { at: Span::DUMMY, path, id, dir: None, next: 0 });
295 let here = Path::new(".");
296 for (offset, file) in order {
297 let at = Span::new(start + offset as u32, start + (offset + file.name.len()) as u32);
298 let form = IncludeForm::Quoted;
299 let found = cx.search.resolve(cx.fs, &file.name, form, Some(here), 0);
300 let Some(found) = found else {
301 let tried = cx.search.tried(&file.name, form, Some(here), 0);
302 self.not_found(&file.name, at, &tried);
303 continue;
304 };
305 let mut discarded = Vec::new();
306 let sink = if file.macros_only { &mut discarded } else { &mut *out };
307 self.read(found, at, sink, cx, &names);
308 }
309 self.stack.clear();
310 Ok(())
311 }
312
313 pub fn run(&mut self, file: FileId, cx: &mut Context<'_>) -> Vec<Tok> {
318 let names = Names::new(cx.interner);
319 let mut out = Vec::new();
320 let name = cx.sources.file(file).name.clone();
321 let dir = directory_of(&name);
322 let path = PathBuf::from(name);
325 let id = cx.fs.identity(&path);
326 self.stack.push(Frame { at: Span::DUMMY, path, id, dir, next: 0 });
327 self.process(file, &mut out, cx, &names);
328 self.stack.clear();
329 out
330 }
331
332 fn process(&mut self, file: FileId, out: &mut Vec<Tok>, cx: &mut Context<'_>, names: &Names) {
334 let bytes = cx.sources.file(file).shared_bytes();
337 let start = cx.sources.file(file).start;
338 let mut reader = Reader::new(bytes.as_slice(), start, cx.lex);
339 let depth_on_entry = self.conds.len();
340 let mut text: Vec<Tok> = Vec::new();
344 let mut body: Vec<PpToken> = Vec::new();
345 let mut scan = Scan::Start;
346
347 loop {
348 let was_live = self.live();
349 let first = reader.next(cx.interner);
350 if first.is_eof() {
351 break;
352 }
353 if is_directive(first) {
354 self.flush(&mut text, out, cx, names);
355 body.clear();
356 let name_tok = reader.next(cx.interner);
357 if name_tok.is_eof() || name_tok.flags.has(TokenFlags::START_OF_LINE) {
360 reader.put_back(name_tok);
361 continue;
362 }
363 body.push(name_tok);
364 if was_live && is_include(ident_of(&name_tok), names) {
369 if let Some(header) = reader.header_name(cx.interner) {
370 body.push(header);
371 }
372 }
373 reader.line(cx.interner, &mut body);
374 let opens =
375 matches!(scan, Scan::Start).then(|| guard_opener(&body, names)).flatten();
376 self.directive(&body, first.span, out, cx, names);
377 scan = match scan {
378 Scan::Start => match opens {
382 Some(name) if self.conds.len() == depth_on_entry + 1 => Scan::Inside(name),
383 _ => Scan::No,
384 },
385 Scan::Inside(name) if self.conds.len() == depth_on_entry => Scan::Closed(name),
386 Scan::Inside(name) => Scan::Inside(name),
387 Scan::Closed(_) | Scan::No => Scan::No,
388 };
389 } else {
390 body.clear();
391 reader.line(cx.interner, &mut body);
392 if self.live() {
393 let operator = ident_of(&first) == Some(names.pragma_op)
399 || body.iter().any(|t| ident_of(t) == Some(names.pragma_op));
400 if operator {
401 self.flush(&mut text, out, cx, names);
402 }
403 text.push(Tok::new(first));
404 text.extend(body.iter().copied().map(Tok::new));
405 if operator {
406 self.flush(&mut text, out, cx, names);
407 }
408 }
409 if !matches!(scan, Scan::Inside(_)) {
411 scan = Scan::No;
412 }
413 }
414 let complaints = reader.take_diagnostics();
417 if was_live || self.live() {
418 self.diagnostics.extend(complaints);
419 }
420 }
421 self.flush(&mut text, out, cx, names);
422 self.diagnostics.extend(reader.take_diagnostics());
423
424 if let Scan::Closed(name) = scan {
427 if self.macros.is_defined(name) {
428 if let Some(frame) = self.stack.last() {
429 self.seen.entry(frame.id.clone()).or_insert(Guard::Macro(name));
430 }
431 }
432 }
433
434 for cond in self.conds.drain(depth_on_entry..) {
437 self.diagnostics
438 .push(Diagnostic::error("unterminated `#if`", cond.span).with_code("E0330"));
439 }
440 }
441
442 fn live(&self) -> bool {
444 self.conds.last().is_none_or(|c| c.live)
445 }
446
447 fn flush(
449 &mut self,
450 text: &mut Vec<Tok>,
451 out: &mut Vec<Tok>,
452 cx: &mut Context<'_>,
453 names: &Names,
454 ) {
455 if text.is_empty() {
456 return;
457 }
458 let taken = std::mem::take(text);
459 let expanded = self.expander.expand_toks(taken, &self.macros, cx.interner, cx.sources);
460 self.diagnostics.append(&mut self.expander.take_diagnostics());
461 let expanded = self.resolve_has(expanded, cx, names, Pass::Text);
466 self.pragma_operator(expanded, out, cx.interner, names);
467 }
468
469 fn directive(
471 &mut self,
472 body: &[PpToken],
473 hash: Span,
474 out: &mut Vec<Tok>,
475 cx: &mut Context<'_>,
476 names: &Names,
477 ) {
478 let Some(first) = body.first().copied() else {
479 return;
480 };
481 let name = ident_of(&first);
482 let rest = &body[1..];
483
484 if name == Some(names.r#if) {
487 let value = self.live() && self.eval(rest, hash, cx, names);
488 self.open(hash, value);
489 return;
490 }
491 if name == Some(names.ifdef) || name == Some(names.ifndef) {
492 let want = name == Some(names.ifdef);
493 let value = self.live() && self.defined_check(rest, hash, want, names);
494 self.open(hash, value);
495 return;
496 }
497 if name == Some(names.elif) || name == Some(names.elifdef) || name == Some(names.elifndef) {
498 self.elif(name, rest, hash, cx, names);
499 return;
500 }
501 if name == Some(names.r#else) {
502 self.branch_else(rest, hash);
503 return;
504 }
505 if name == Some(names.endif) {
506 self.endif(rest, hash);
507 return;
508 }
509 if !self.live() {
510 return;
514 }
515
516 if name.is_none() && decimal(&first, cx.interner).is_some() {
520 self.line_marker(body, hash, out.len(), cx);
521 return;
522 }
523
524 let interner = &mut *cx.interner;
525 if name == Some(names.define) {
526 let (def, diagnostics) = parse_define(rest, interner);
527 self.diagnostics.extend(diagnostics);
528 if let Some(def) = def {
529 if let Some(problem) = self.macros.define(def, interner) {
530 self.diagnostics.push(problem);
531 }
532 }
533 } else if name == Some(names.undef) {
534 self.undef(rest, hash, interner);
535 } else if name == Some(names.error) || name == Some(names.warning) {
536 self.message(rest, hash, name == Some(names.error), interner);
537 } else if name == Some(names.line) {
538 self.line(rest, hash, out.len(), cx);
539 } else if name == Some(names.pragma) {
540 if rest.len() == 1 && ident_of(&rest[0]) == Some(names.once) {
547 self.pragma_once(rest[0].span);
548 } else if !self.macro_stack_pragma(rest, hash, interner, names) {
549 self.pass_through(body, hash, out);
550 }
551 } else if name == Some(names.include) || name == Some(names.include_next) {
552 self.include(rest, hash, name == Some(names.include_next), out, cx, names);
553 } else if name == Some(names.embed) {
554 self.embed(rest, hash, out, cx);
555 } else {
556 self.diagnostics.push(
557 Diagnostic::error("invalid preprocessing directive", first.span).with_code("E0332"),
558 );
559 }
560 }
561
562 fn macro_stack_pragma(
574 &mut self,
575 rest: &[PpToken],
576 at: Span,
577 interner: &mut Interner,
578 names: &Names,
579 ) -> bool {
580 let which = match rest.first().and_then(ident_of) {
581 Some(name) if name == names.push_macro => names.push_macro,
582 Some(name) if name == names.pop_macro => names.pop_macro,
583 _ => return false,
584 };
585 let word = if which == names.push_macro { "push_macro" } else { "pop_macro" };
586 let [_, open, text, close, extra @ ..] = rest else {
591 self.invalid_pragma(word, at);
592 return true;
593 };
594 if open.punct() != Some(Punct::LParen)
595 || text.kind != PpTokenKind::StringLit
596 || close.punct() != Some(Punct::RParen)
597 {
598 self.invalid_pragma(word, at);
599 return true;
600 }
601 self.extra_tokens(extra, "#pragma");
602 let Some(name) = identifier_in(*text, interner) else {
607 return true;
608 };
609 if which == names.push_macro {
610 self.macros.push_macro(name);
611 } else {
612 self.macros.pop_macro(name);
613 }
614 true
615 }
616
617 fn invalid_pragma(&mut self, word: &str, at: Span) {
618 self.diagnostics.push(
619 Diagnostic::error(format!("invalid `#pragma {word}` directive"), at).with_code("E0672"),
620 );
621 }
622
623 fn pragma_once(&mut self, at: Span) {
625 if self.stack.len() <= 1 {
630 self.diagnostics.push(
631 Diagnostic::warning("`#pragma once` in the main file", at).with_code("W0332"),
632 );
633 }
634 if let Some(frame) = self.stack.last() {
635 self.seen.insert(frame.id.clone(), Guard::Once);
636 }
637 }
638
639 fn skip(&self, id: &Path) -> bool {
641 match self.seen.get(id) {
642 Some(Guard::Once) => true,
643 Some(Guard::Macro(name)) => self.macros.is_defined(*name),
644 None => false,
645 }
646 }
647
648 fn pass_through(&mut self, body: &[PpToken], hash: Span, out: &mut Vec<Tok>) {
650 let _ = self;
651 out.push(Tok::synthetic(
652 PpTokenKind::Punct(Punct::Hash),
653 None,
654 TokenFlags::START_OF_LINE,
655 hash,
656 ));
657 for (at, token) in body.iter().copied().enumerate() {
662 let mut token = Tok::new(token);
663 if at == 0 {
664 token.flags = token.flags.without(TokenFlags::LEADING_SPACE);
665 }
666 out.push(token);
667 }
668 }
669
670 fn include(
672 &mut self,
673 rest: &[PpToken],
674 hash: Span,
675 is_next: bool,
676 out: &mut Vec<Tok>,
677 cx: &mut Context<'_>,
678 names: &Names,
679 ) {
680 let Some(header) = self.header_of(rest, hash, cx) else {
681 return;
682 };
683 let (form, relative_to, from) = self.where_to_look(&header, is_next, cx);
684 let found = cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from);
685 let Some(found) = found else {
686 let tried = cx.search.tried(&header.name, form, relative_to.as_deref(), from);
687 self.not_found(&header.name, hash, &tried);
688 return;
689 };
690 self.read(found, hash, out, cx, names);
691 }
692
693 fn not_found(&mut self, name: &str, at: Span, tried: &[PathBuf]) {
695 let where_looked = if tried.is_empty() && Path::new(name).is_absolute() {
699 "the name is an absolute path, so the search path was not used".to_owned()
700 } else if tried.is_empty() {
701 "the include search path is empty".to_owned()
702 } else {
703 let list: Vec<String> =
704 tried.iter().map(|d| d.to_string_lossy().into_owned()).collect();
705 format!("searched: {}", list.join(", "))
706 };
707 self.diagnostics.push(
708 Diagnostic::error(format!("`{name}` file not found"), at)
709 .with_code("E0341")
710 .note(where_looked, at),
711 );
712 }
713
714 fn read(
721 &mut self,
722 found: Found,
723 at: Span,
724 out: &mut Vec<Tok>,
725 cx: &mut Context<'_>,
726 names: &Names,
727 ) {
728 let id = cx.fs.identity(&found.path);
729 if self.dep_ids.insert(id.clone()) {
734 let path = rucc_session::path_key(&found.path);
741 self.deps.push(Dependency { path, is_system: found.is_system });
742 }
743 if self.skip(&id) {
748 return;
749 }
750 if self.stack.len() >= cx.max_include_depth as usize {
751 let mut diagnostic = Diagnostic::error("`#include` nested too deeply", at)
752 .with_code("E0342")
753 .note("a header that includes itself with no include guard is the usual cause", at);
754 if let Some(outer) = self.stack.first().filter(|f| !f.at.is_dummy()) {
755 diagnostic = diagnostic.note("the outermost include is here", outer.at);
756 }
757 self.diagnostics.push(diagnostic);
758 return;
759 }
760 let added = cx.sources.add_shared(found.name.clone(), found.bytes.clone(), Some(at));
761 let file = match added {
762 Ok(file) => file,
763 Err(full) => {
764 self.diagnostics.push(Diagnostic::error(full.to_string(), at).with_code("E0344"));
765 return;
766 }
767 };
768 self.stack.push(Frame {
769 at,
770 dir: found.path.parent().map(Path::to_path_buf),
771 id,
772 path: found.path,
773 next: found.next,
774 });
775 self.process(file, out, cx, names);
776 self.stack.pop();
777 }
778
779 fn embed(&mut self, rest: &[PpToken], hash: Span, out: &mut Vec<Tok>, cx: &mut Context<'_>) {
781 let Some((header, params)) = self.embed_line(rest, hash, cx) else {
782 return;
783 };
784 let Some(found) = self.find(&header, false, cx) else {
785 self.diagnostics.push(
786 Diagnostic::error(format!("`{}` resource not found", header.name), hash)
787 .with_code("E0341")
788 .note("an `#embed` resource is looked for on the include path", hash),
789 );
790 return;
791 };
792 embed::tokens(found.bytes.as_slice(), ¶ms, hash, cx.interner, out);
797 }
798
799 fn embed_line(
801 &mut self,
802 rest: &[PpToken],
803 hash: Span,
804 cx: &mut Context<'_>,
805 ) -> Option<(Header, embed::Params)> {
806 if rest.is_empty() {
807 self.bad_header(hash);
808 return None;
809 }
810 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
811 let line = if line[0].kind == PpTokenKind::HeaderName {
817 line
818 } else {
819 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
820 self.diagnostics.append(&mut self.expander.take_diagnostics());
821 expanded
822 };
823 let Some(used) = embed::header_length(&line) else {
824 self.bad_header(line.first().map_or(hash, |t| t.report_span()));
825 return None;
826 };
827 let header = if line[0].kind == PpTokenKind::HeaderName {
828 header_from_token(spelling(line[0], cx.interner))
829 } else {
830 let spellings: Vec<&str> =
831 line[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
832 header_from_tokens(&spellings)
833 };
834 let Some(header) = header else {
835 self.bad_header(line[0].report_span());
836 return None;
837 };
838 let params = self.embed_params(&line[used..], hash, cx)?;
839 Some((header, params))
840 }
841
842 fn embed_params(
844 &mut self,
845 line: &[Tok],
846 at: Span,
847 cx: &mut Context<'_>,
848 ) -> Option<embed::Params> {
849 let Preprocessor { expander, macros, diagnostics, .. } = self;
850 let sources = &mut *cx.sources;
851 let mut expand = |toks: Vec<Tok>, interner: &mut Interner| {
852 expander.expand_toks(toks, macros, interner, sources)
853 };
854 let params = embed::parse(line, at, cx.interner, diagnostics, &mut expand);
855 self.diagnostics.append(&mut self.expander.take_diagnostics());
856 params
857 }
858
859 fn where_to_look(
870 &self,
871 header: &Header,
872 is_next: bool,
873 cx: &Context<'_>,
874 ) -> (IncludeForm, Option<PathBuf>, usize) {
875 let form = if header.angled { IncludeForm::Angled } else { IncludeForm::Quoted };
876 let frame = self.stack.last();
877 let from = if is_next {
878 frame.map_or(0, |f| f.next).max(cx.search.start(form))
879 } else {
880 cx.search.start(form)
881 };
882 let relative_to = if is_next { None } else { frame.and_then(|f| f.dir.clone()) };
883 (form, relative_to, from)
884 }
885
886 fn find(&self, header: &Header, is_next: bool, cx: &Context<'_>) -> Option<Found> {
888 let (form, relative_to, from) = self.where_to_look(header, is_next, cx);
889 cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from)
890 }
891
892 fn header_of(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>) -> Option<Header> {
894 if let Some(first) = rest.first().copied() {
895 if first.kind == PpTokenKind::HeaderName {
896 let text = first.value.map_or("", |v| cx.interner.resolve(v));
897 let header = header_from_token(text);
898 if header.is_none() {
899 self.bad_header(first.span);
900 }
901 self.extra_tokens(&rest[1..], "#include");
902 return header;
903 }
904 }
905 if rest.is_empty() {
909 self.bad_header(hash);
910 return None;
911 }
912 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
913 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
914 self.diagnostics.append(&mut self.expander.take_diagnostics());
915 let spellings: Vec<&str> = expanded.iter().map(|t| spelling(*t, cx.interner)).collect();
916 let header = header_from_tokens(&spellings);
917 if header.is_none() {
918 let at = expanded.first().map_or(hash, |t| t.report_span());
919 self.bad_header(at);
920 }
921 header
922 }
923
924 fn bad_operand(&mut self, tok: Tok, at: Span, interner: &Interner) {
926 self.diagnostics.push(
927 Diagnostic::error(
928 format!("expected an identifier as the operand of `{}`", spelling(tok, interner)),
929 at,
930 )
931 .with_code("E0345"),
932 );
933 }
934
935 fn bad_header(&mut self, at: Span) {
936 self.diagnostics.push(
937 Diagnostic::error("expected a file name in `<>` or `\"\"`", at).with_code("E0343"),
938 );
939 }
940
941 fn open(&mut self, span: Span, value: bool) {
943 let enclosing_live = self.live();
944 self.conds.push(Cond {
945 span,
946 live: enclosing_live && value,
947 taken: value,
948 enclosing_live,
949 seen_else: false,
950 });
951 }
952
953 fn elif(
954 &mut self,
955 name: Option<Symbol>,
956 rest: &[PpToken],
957 hash: Span,
958 cx: &mut Context<'_>,
959 names: &Names,
960 ) {
961 let Some(top) = self.conds.last() else {
962 self.stray("elif", hash);
963 return;
964 };
965 if top.seen_else {
966 self.diagnostics
967 .push(Diagnostic::error("`#elif` after `#else`", hash).with_code("E0333"));
968 return;
969 }
970 let (enclosing_live, already_taken) = (top.enclosing_live, top.taken);
973 let consider = enclosing_live && !already_taken;
974 let value = if !consider {
975 false
976 } else if name == Some(names.elif) {
977 self.eval(rest, hash, cx, names)
978 } else {
979 self.defined_check(rest, hash, name == Some(names.elifdef), names)
980 };
981 let top = self.conds.last_mut().expect("checked above and nothing popped");
982 top.live = consider && value;
983 top.taken = already_taken || value;
984 }
985
986 fn branch_else(&mut self, rest: &[PpToken], hash: Span) {
987 let Some(top) = self.conds.last_mut() else {
988 self.stray("else", hash);
989 return;
990 };
991 if top.seen_else {
992 self.diagnostics.push(Diagnostic::error("a second `#else`", hash).with_code("E0333"));
993 return;
994 }
995 top.live = top.enclosing_live && !top.taken;
996 top.taken = true;
997 top.seen_else = true;
998 let enclosing_live = top.enclosing_live;
999 if enclosing_live {
1000 self.extra_tokens(rest, "#else");
1001 }
1002 }
1003
1004 fn endif(&mut self, rest: &[PpToken], hash: Span) {
1005 if self.conds.pop().is_none() {
1006 self.stray("endif", hash);
1007 return;
1008 }
1009 if self.live() {
1010 self.extra_tokens(rest, "#endif");
1011 }
1012 }
1013
1014 fn stray(&mut self, what: &str, hash: Span) {
1015 self.diagnostics
1016 .push(Diagnostic::error(format!("`#{what}` without `#if`"), hash).with_code("E0334"));
1017 }
1018
1019 fn extra_tokens(&mut self, rest: &[PpToken], what: &str) {
1024 if let Some(first) = rest.first() {
1025 self.diagnostics.push(
1026 Diagnostic::warning(format!("extra tokens after `{what}`"), first.span)
1027 .with_code("W0330"),
1028 );
1029 }
1030 }
1031
1032 fn eval(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>, names: &Names) -> bool {
1034 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
1035 let line = self.resolve_defined(line, cx.interner, names);
1041 let line = self.resolve_has(line, cx, names, Pass::Headers);
1046 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
1047 self.diagnostics.append(&mut self.expander.take_diagnostics());
1048 let line = self.resolve_defined(line, cx.interner, names);
1049 let line = self.resolve_has(line, cx, names, Pass::Rest);
1050 cond::evaluate(&line, cx.interner, &mut self.diagnostics, hash)
1051 }
1052
1053 fn resolve_has(
1058 &mut self,
1059 line: Vec<Tok>,
1060 cx: &mut Context<'_>,
1061 names: &Names,
1062 pass: Pass,
1063 ) -> Vec<Tok> {
1064 if !line.iter().any(|t| t.ident().is_some_and(|n| names.has.op(n).is_some())) {
1065 return line;
1066 }
1067 let mut out = Vec::with_capacity(line.len());
1068 let mut at = 0;
1069 while at < line.len() {
1070 let tok = line[at];
1071 let op = tok.ident().and_then(|n| names.has.op(n));
1072 let Some(op) = op.filter(|op| pass.answers(*op)) else {
1073 if pass == Pass::Text && op.is_some_and(Op::is_header) {
1074 self.outside_a_directive(tok, cx);
1075 }
1076 out.push(tok);
1077 at += 1;
1078 continue;
1079 };
1080 let Some((operand, after)) = arguments(&line, at + 1) else {
1081 if pass != Pass::Headers {
1085 self.diagnostics.push(
1086 Diagnostic::error(
1087 format!("expected `(` after `{}`", spelling(tok, cx.interner)),
1088 tok.report_span(),
1089 )
1090 .with_code("E0345"),
1091 );
1092 }
1093 out.push(tok);
1094 at += 1;
1095 continue;
1096 };
1097 at = after;
1098 let value = self.ask(op, operand, tok, cx);
1101 let sym = cx.interner.intern(&value.to_string());
1102 out.push(Tok::synthetic(PpTokenKind::Number, Some(sym), tok.flags, tok.report_span()));
1103 }
1104 out
1105 }
1106
1107 fn outside_a_directive(&mut self, tok: Tok, cx: &Context<'_>) {
1115 self.diagnostics.push(
1116 Diagnostic::error(
1117 format!(
1118 "`{}` used outside of a preprocessing directive",
1119 spelling(tok, cx.interner)
1120 ),
1121 tok.report_span(),
1122 )
1123 .with_code("E0350"),
1124 );
1125 }
1126
1127 fn ask(&mut self, op: Op, operand: &[Tok], tok: Tok, cx: &mut Context<'_>) -> u32 {
1129 let at = operand.first().map_or(tok.report_span(), |t| t.report_span());
1130 match op {
1131 Op::Include | Op::IncludeNext => {
1132 let spellings: Vec<&str> =
1133 operand.iter().map(|t| spelling(*t, cx.interner)).collect();
1134 let Some(header) = header_from_tokens(&spellings) else {
1135 self.bad_header(at);
1136 return 0;
1137 };
1138 u32::from(self.find(&header, op == Op::IncludeNext, cx).is_some())
1139 }
1140 Op::Embed => {
1141 let Some(used) = embed::header_length(operand) else {
1146 self.bad_header(at);
1147 return 0;
1148 };
1149 let header = if operand[0].kind == PpTokenKind::HeaderName {
1150 header_from_token(spelling(operand[0], cx.interner))
1151 } else {
1152 let spellings: Vec<&str> =
1153 operand[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
1154 header_from_tokens(&spellings)
1155 };
1156 let Some(header) = header else {
1157 self.bad_header(at);
1158 return 0;
1159 };
1160 let Some(params) = self.embed_params(&operand[used..], at, cx) else {
1165 return 0;
1166 };
1167 match self.find(&header, false, cx) {
1168 None => 0,
1169 Some(found) => {
1170 let taken = params.taken(found.bytes.as_slice().len() as u64);
1171 if taken == 0 { 2 } else { 1 }
1172 }
1173 }
1174 }
1175 Op::BuildingModule => {
1176 if attribute_name(operand, cx.interner).is_none() {
1177 self.bad_operand(tok, at, cx.interner);
1178 }
1179 0
1186 }
1187 Op::Table(kind) => {
1188 let Some(name) = attribute_name(operand, cx.interner) else {
1189 self.bad_operand(tok, at, cx.interner);
1190 return 0;
1191 };
1192 match kind {
1193 Kind::Attribute => rucc_gnu::has_attribute(name),
1194 Kind::CAttribute => rucc_gnu::has_c_attribute(name),
1195 Kind::Builtin => rucc_gnu::has_builtin(name),
1196 Kind::Feature => rucc_gnu::has_feature(name),
1197 Kind::Extension => rucc_gnu::has_extension(name),
1198 }
1199 }
1200 }
1201 }
1202
1203 fn resolve_defined(
1205 &mut self,
1206 line: Vec<Tok>,
1207 interner: &mut Interner,
1208 names: &Names,
1209 ) -> Vec<Tok> {
1210 if !line.iter().any(|t| t.ident() == Some(names.defined)) {
1211 return line;
1212 }
1213 let mut out = Vec::with_capacity(line.len());
1214 let mut at = 0;
1215 while at < line.len() {
1216 let tok = line[at];
1217 if tok.ident() != Some(names.defined) {
1218 out.push(tok);
1219 at += 1;
1220 continue;
1221 }
1222 let parenthesised = line.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1223 let name_at = if parenthesised { at + 2 } else { at + 1 };
1224 let name = line.get(name_at).and_then(|t| t.ident());
1225 let Some(name) = name else {
1226 self.diagnostics.push(
1227 Diagnostic::error("`defined` without a macro name", tok.report_span())
1228 .with_code("E0335"),
1229 );
1230 out.push(tok);
1231 at += 1;
1232 continue;
1233 };
1234 at = name_at + 1;
1235 if parenthesised {
1236 if line.get(at).is_some_and(|t| t.is(Punct::RParen)) {
1237 at += 1;
1238 } else {
1239 self.diagnostics.push(
1240 Diagnostic::error("expected `)` after `defined`", tok.report_span())
1241 .with_code("E0335"),
1242 );
1243 }
1244 }
1245 let value = self.macros.is_defined(name) || names.has.op(name).is_some();
1249 out.push(number(value, tok.flags, tok.report_span(), interner));
1250 }
1251 out
1252 }
1253
1254 fn defined_check(
1256 &mut self,
1257 rest: &[PpToken],
1258 hash: Span,
1259 want_defined: bool,
1260 names: &Names,
1261 ) -> bool {
1262 let Some(name) = rest.first().and_then(ident_of) else {
1263 self.diagnostics.push(
1264 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1265 .with_code("E0336"),
1266 );
1267 return false;
1268 };
1269 self.extra_tokens(&rest[1..], if want_defined { "#ifdef" } else { "#ifndef" });
1270 let defined = self.macros.is_defined(name) || names.has.op(name).is_some();
1271 defined == want_defined
1272 }
1273
1274 fn undef(&mut self, rest: &[PpToken], hash: Span, interner: &Interner) {
1275 let Some(name) = rest.first().and_then(ident_of) else {
1276 self.diagnostics.push(
1277 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1278 .with_code("E0336"),
1279 );
1280 return;
1281 };
1282 let text = interner.resolve(name);
1285 if text == "defined" || text.starts_with("__STDC_") {
1286 self.diagnostics.push(
1287 Diagnostic::error(format!("`{text}` cannot be undefined"), rest[0].span)
1288 .with_code("E0337"),
1289 );
1290 return;
1291 }
1292 self.macros.undef(name);
1293 self.extra_tokens(&rest[1..], "#undef");
1294 }
1295
1296 fn message(&mut self, rest: &[PpToken], hash: Span, fatal: bool, interner: &Interner) {
1298 let text = spell_line(rest, interner);
1299 let span = rest.first().map_or(hash, |t| t.span.to(last_span(rest)));
1300 let diag = if fatal {
1301 Diagnostic::error(text, span).with_code("E0338")
1302 } else {
1303 Diagnostic::warning(text, span).with_code("W0331")
1304 };
1305 self.diagnostics.push(diag);
1306 }
1307
1308 fn line(&mut self, rest: &[PpToken], hash: Span, at: usize, cx: &mut Context<'_>) {
1313 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
1314 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
1315 self.diagnostics.append(&mut self.expander.take_diagnostics());
1316 let interner = &mut *cx.interner;
1317
1318 let number_text = line
1319 .first()
1320 .filter(|t| t.kind == PpTokenKind::Number)
1321 .and_then(|t| t.value)
1322 .map(|v| interner.resolve(v));
1323 let Some(parsed) = number_text.and_then(|t| t.parse::<u64>().ok()) else {
1324 self.diagnostics.push(
1325 Diagnostic::error(
1326 "`#line` needs a decimal line number",
1327 line.first().map_or(hash, |t| t.report_span()),
1328 )
1329 .with_code("E0339"),
1330 );
1331 return;
1332 };
1333 if parsed == 0 || parsed > 2_147_483_647 {
1336 self.diagnostics.push(
1337 Diagnostic::error("`#line` number is out of range", line[0].report_span())
1338 .with_code("E0339"),
1339 );
1340 return;
1341 }
1342
1343 let mut file = None;
1344 if let Some(second) = line.get(1) {
1345 if second.kind == PpTokenKind::StringLit {
1346 file = second.value;
1347 } else {
1348 self.diagnostics.push(
1349 Diagnostic::error(
1350 "`#line` file name must be a string literal",
1351 second.report_span(),
1352 )
1353 .with_code("E0339"),
1354 );
1355 return;
1356 }
1357 }
1358 if let Some(extra) = line.get(2) {
1359 self.diagnostics.push(
1360 Diagnostic::warning("extra tokens after `#line`", extra.report_span())
1361 .with_code("W0330"),
1362 );
1363 }
1364 #[expect(
1365 clippy::cast_possible_truncation,
1366 reason = "the range check above keeps this inside i32, let alone u32"
1367 )]
1368 let number = parsed as u32;
1369 self.lines.push(LineDirective { span: hash, line: number, file, at });
1370 let name = file.map(|v| destringize(cx.interner.resolve(v)));
1371 cx.sources.set_presumed(hash.lo, number, name);
1372 }
1373
1374 fn line_marker(&mut self, body: &[PpToken], hash: Span, at: usize, cx: &mut Context<'_>) {
1391 let Some(number) = decimal(&body[0], cx.interner) else { return };
1392 let mut rest = &body[1..];
1393 let mut file = None;
1394 if let Some(first) = rest.first().filter(|t| t.kind == PpTokenKind::StringLit) {
1395 file = first.value;
1396 rest = &rest[1..];
1397 }
1398
1399 let (mut entering, mut leaving) = (false, false);
1400 for flag in rest {
1401 match decimal(flag, cx.interner) {
1402 Some(1) => entering = true,
1403 Some(2) => leaving = true,
1404 Some(3 | 4) => {}
1405 _ => {
1406 let text = spell_line(std::slice::from_ref(flag), cx.interner);
1407 self.diagnostics.push(
1408 Diagnostic::error(
1409 format!("invalid flag `{text}` in line directive"),
1410 flag.span,
1411 )
1412 .with_code("E0339"),
1413 );
1414 return;
1415 }
1416 }
1417 }
1418
1419 let name = file.map(|v| destringize(cx.interner.resolve(v)));
1420 if leaving {
1421 if let Some(name) = &name {
1422 if !self.leave_marker(name) {
1423 self.diagnostics.push(
1424 Diagnostic::warning(
1425 format!("file `{name}` linemarker ignored due to incorrect nesting"),
1426 last_span(body),
1427 )
1428 .with_code("W0330"),
1429 );
1430 return;
1431 }
1432 } else {
1433 self.markers.pop();
1434 }
1435 }
1436 if entering {
1437 let here = cx.sources.presumed(hash.lo).map(|loc| loc.name.to_owned());
1438 self.markers.push(here.unwrap_or_default());
1439 }
1440
1441 self.lines.push(LineDirective { span: hash, line: number, file, at });
1442 cx.sources.set_presumed(hash.lo, number, name);
1443 }
1444
1445 fn leave_marker(&mut self, name: &str) -> bool {
1459 if let Some(at) = self.markers.iter().rposition(|outer| outer == name) {
1460 self.markers.truncate(at);
1461 return true;
1462 }
1463 let found = self.stack.iter().rev().skip(1).any(|f| f.path.as_os_str() == name);
1466 if found {
1467 self.markers.clear();
1468 }
1469 found
1470 }
1471
1472 fn pragma_operator(
1478 &mut self,
1479 expanded: Vec<Tok>,
1480 out: &mut Vec<Tok>,
1481 interner: &mut Interner,
1482 names: &Names,
1483 ) {
1484 if !expanded.iter().any(|t| t.ident() == Some(names.pragma_op)) {
1485 out.extend(expanded);
1486 return;
1487 }
1488 let mut at = 0;
1489 let mut ends_a_line = false;
1494 while at < expanded.len() {
1495 let mut tok = expanded[at];
1496 if tok.ident() != Some(names.pragma_op) {
1497 if ends_a_line {
1498 tok.flags = tok.flags.with(TokenFlags::START_OF_LINE);
1499 ends_a_line = false;
1500 }
1501 out.push(tok);
1502 at += 1;
1503 continue;
1504 }
1505 let open = expanded.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1506 let text = expanded.get(at + 2).filter(|t| t.kind == PpTokenKind::StringLit);
1507 let close = expanded.get(at + 3).is_some_and(|t| t.is(Punct::RParen));
1508 let (Some(text), true, true) = (text, open, close) else {
1509 self.diagnostics.push(
1510 Diagnostic::error("`_Pragma` takes a single string literal", tok.report_span())
1511 .with_code("E0340"),
1512 );
1513 out.push(tok);
1514 at += 1;
1515 continue;
1516 };
1517 let literal = text.value.map(|v| interner.resolve(v)).unwrap_or_default();
1518 let body = destringize(literal);
1519 self.emit_pragma(&body, tok, out, interner, names);
1520 ends_a_line = true;
1521 at += 4;
1522 }
1523 }
1524
1525 fn emit_pragma(
1527 &mut self,
1528 body: &str,
1529 at: Tok,
1530 out: &mut Vec<Tok>,
1531 interner: &mut Interner,
1532 names: &Names,
1533 ) {
1534 let span = at.report_span();
1535 let (tokens, diagnostics) = tokenize(body.as_bytes(), 0, Options::new(), interner);
1536 self.diagnostics.extend(
1539 diagnostics
1540 .into_iter()
1541 .map(|d| Diagnostic::new(d.severity, d.message, span).with_code("E0340")),
1542 );
1543 let tokens: Vec<PpToken> = tokens.into_iter().filter(|t| !t.is_eof()).collect();
1544 if self.macro_stack_pragma(&tokens, span, interner, names) {
1548 return;
1549 }
1550 out.push(Tok::synthetic(
1551 PpTokenKind::Punct(Punct::Hash),
1552 None,
1553 TokenFlags::START_OF_LINE,
1554 span,
1555 ));
1556 out.push(Tok::synthetic(PpTokenKind::Ident, Some(names.pragma), TokenFlags::EMPTY, span));
1557 for (at, t) in tokens.into_iter().enumerate() {
1561 let spaced = at == 0 || t.flags.has(TokenFlags::LEADING_SPACE);
1564 let flags = if spaced {
1565 TokenFlags::EMPTY.with(TokenFlags::LEADING_SPACE)
1566 } else {
1567 TokenFlags::EMPTY
1568 };
1569 out.push(Tok::synthetic(t.kind, t.value, flags, span));
1570 }
1571 }
1572}
1573
1574fn guard_opener(body: &[PpToken], names: &Names) -> Option<Symbol> {
1579 let name = ident_of(body.first()?)?;
1580 let rest = &body[1..];
1581 if name == names.ifndef {
1582 let [only] = rest else {
1583 return None;
1584 };
1585 return ident_of(only);
1586 }
1587 if name != names.r#if {
1588 return None;
1589 }
1590 let [bang, defined, tail @ ..] = rest else {
1591 return None;
1592 };
1593 if bang.punct() != Some(Punct::Bang) || ident_of(defined) != Some(names.defined) {
1594 return None;
1595 }
1596 match tail {
1597 [only] => ident_of(only),
1598 [open, only, close]
1599 if open.punct() == Some(Punct::LParen) && close.punct() == Some(Punct::RParen) =>
1600 {
1601 ident_of(only)
1602 }
1603 _ => None,
1604 }
1605}
1606
1607fn is_include(name: Option<Symbol>, names: &Names) -> bool {
1609 name == Some(names.include) || name == Some(names.include_next) || name == Some(names.embed)
1610}
1611
1612fn is_directive(tok: PpToken) -> bool {
1614 tok.flags.has(TokenFlags::START_OF_LINE) && tok.punct() == Some(Punct::Hash)
1615}
1616
1617fn ident_of(tok: &PpToken) -> Option<Symbol> {
1618 match tok.kind {
1619 PpTokenKind::Ident => tok.value,
1620 _ => None,
1621 }
1622}
1623
1624fn last_span(tokens: &[PpToken]) -> Span {
1625 tokens.last().map_or(Span::DUMMY, |t| t.span)
1626}
1627
1628fn decimal(tok: &PpToken, interner: &Interner) -> Option<u32> {
1634 if tok.kind != PpTokenKind::Number {
1635 return None;
1636 }
1637 let text = interner.resolve(tok.value?);
1638 if text.is_empty() || !text.bytes().all(|b| b.is_ascii_digit()) {
1639 return None;
1640 }
1641 text.parse::<u32>().ok().filter(|n| *n <= 2_147_483_647)
1644}
1645
1646fn number(value: bool, flags: TokenFlags, span: Span, interner: &mut Interner) -> Tok {
1648 let sym = interner.intern(if value { "1" } else { "0" });
1649 Tok::synthetic(PpTokenKind::Number, Some(sym), flags, span)
1650}
1651
1652fn spell_line(tokens: &[PpToken], interner: &Interner) -> String {
1654 let mut out = String::new();
1655 for (index, tok) in tokens.iter().enumerate() {
1656 if index > 0 && tok.flags.has(TokenFlags::LEADING_SPACE) {
1657 out.push(' ');
1658 }
1659 match tok.value {
1660 Some(sym) => out.push_str(interner.resolve(sym)),
1661 None => {
1662 if let Some(p) = tok.punct() {
1663 out.push_str(p.as_str());
1664 }
1665 }
1666 }
1667 }
1668 out
1669}
1670
1671fn identifier_in(text: PpToken, interner: &mut Interner) -> Option<Symbol> {
1676 let literal = interner.resolve(text.value?).to_string();
1677 let (tokens, _) = tokenize(destringize(&literal).as_bytes(), 0, Options::new(), interner);
1678 let mut real = tokens.into_iter().filter(|t| !t.is_eof());
1679 let first = real.next()?;
1680 if first.kind != PpTokenKind::Ident || real.next().is_some() {
1681 return None;
1682 }
1683 first.value
1684}
1685
1686fn destringize(literal: &str) -> String {
1691 let body = literal
1692 .trim_start_matches(['L', 'u', 'U', '8'])
1693 .strip_prefix('"')
1694 .and_then(|s| s.strip_suffix('"'))
1695 .unwrap_or(literal);
1696 let mut out = String::with_capacity(body.len());
1697 let mut chars = body.chars();
1698 while let Some(c) = chars.next() {
1699 if c != '\\' {
1700 out.push(c);
1701 continue;
1702 }
1703 match chars.next() {
1704 Some('"') => out.push('"'),
1705 Some('\\') => out.push('\\'),
1706 Some(other) => {
1707 out.push('\\');
1708 out.push(other);
1709 }
1710 None => out.push('\\'),
1711 }
1712 }
1713 out
1714}
1715
1716fn arguments(line: &[Tok], at: usize) -> Option<(&[Tok], usize)> {
1722 if !line.get(at)?.is(Punct::LParen) {
1723 return None;
1724 }
1725 let mut depth = 1u32;
1726 let mut end = at + 1;
1727 while end < line.len() {
1728 if line[end].is(Punct::LParen) {
1729 depth += 1;
1730 } else if line[end].is(Punct::RParen) {
1731 depth -= 1;
1732 if depth == 0 {
1733 return Some((&line[at + 1..end], end + 1));
1734 }
1735 }
1736 end += 1;
1737 }
1738 None
1739}
1740
1741fn attribute_name<'i>(operand: &[Tok], interner: &'i Interner) -> Option<&'i str> {
1747 let name = match operand {
1748 [one] => one,
1749 [_, scope, name] if scope.is(Punct::ColonColon) => name,
1750 _ => return None,
1751 };
1752 name.ident().map(|sym| interner.resolve(sym))
1753}
1754
1755#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1762enum Pass {
1763 Headers,
1765 Rest,
1768 Text,
1770}
1771
1772impl Pass {
1773 fn answers(self, op: Op) -> bool {
1775 match self {
1776 Pass::Headers => op.is_header(),
1777 Pass::Rest => true,
1778 Pass::Text => !op.is_header(),
1779 }
1780 }
1781}
1782
1783#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1785enum Op {
1786 Include,
1788 IncludeNext,
1790 Embed,
1793 BuildingModule,
1795 Table(Kind),
1797}
1798
1799impl Op {
1800 fn is_header(self) -> bool {
1802 matches!(self, Op::Include | Op::IncludeNext | Op::Embed)
1803 }
1804}
1805
1806struct HasOps {
1811 ops: [(Symbol, Op); 9],
1812 range: (Symbol, Symbol),
1819}
1820
1821impl HasOps {
1822 fn new(interner: &mut Interner) -> HasOps {
1823 let ops = [
1824 (interner.intern("__has_include"), Op::Include),
1825 (interner.intern("__has_include_next"), Op::IncludeNext),
1826 (interner.intern("__has_embed"), Op::Embed),
1827 (interner.intern("__has_attribute"), Op::Table(Kind::Attribute)),
1828 (interner.intern("__has_c_attribute"), Op::Table(Kind::CAttribute)),
1829 (interner.intern("__has_builtin"), Op::Table(Kind::Builtin)),
1830 (interner.intern("__has_feature"), Op::Table(Kind::Feature)),
1831 (interner.intern("__has_extension"), Op::Table(Kind::Extension)),
1832 (interner.intern("__building_module"), Op::BuildingModule),
1833 ];
1834 let mut range = (ops[0].0, ops[0].0);
1835 for &(sym, _) in &ops {
1836 range = (range.0.min(sym), range.1.max(sym));
1837 }
1838 HasOps { ops, range }
1839 }
1840
1841 #[inline]
1843 fn op(&self, name: Symbol) -> Option<Op> {
1844 if name < self.range.0 || name > self.range.1 {
1845 return None;
1846 }
1847 self.ops.iter().find(|(sym, _)| *sym == name).map(|(_, op)| *op)
1848 }
1849}
1850
1851struct Names {
1857 define: Symbol,
1858 undef: Symbol,
1859 r#if: Symbol,
1860 ifdef: Symbol,
1861 ifndef: Symbol,
1862 elif: Symbol,
1863 elifdef: Symbol,
1864 elifndef: Symbol,
1865 r#else: Symbol,
1866 endif: Symbol,
1867 line: Symbol,
1868 error: Symbol,
1869 warning: Symbol,
1870 pragma: Symbol,
1871 include: Symbol,
1872 include_next: Symbol,
1873 embed: Symbol,
1874 defined: Symbol,
1875 once: Symbol,
1876 push_macro: Symbol,
1877 pop_macro: Symbol,
1878 pragma_op: Symbol,
1879 has: HasOps,
1880}
1881
1882impl Names {
1883 fn new(interner: &mut Interner) -> Names {
1884 Names {
1885 define: interner.intern("define"),
1886 undef: interner.intern("undef"),
1887 r#if: interner.intern("if"),
1888 ifdef: interner.intern("ifdef"),
1889 ifndef: interner.intern("ifndef"),
1890 elif: interner.intern("elif"),
1891 elifdef: interner.intern("elifdef"),
1892 elifndef: interner.intern("elifndef"),
1893 r#else: interner.intern("else"),
1894 endif: interner.intern("endif"),
1895 line: interner.intern("line"),
1896 error: interner.intern("error"),
1897 warning: interner.intern("warning"),
1898 pragma: interner.intern("pragma"),
1899 include: interner.intern("include"),
1900 include_next: interner.intern("include_next"),
1901 embed: interner.intern("embed"),
1902 defined: interner.intern("defined"),
1903 once: interner.intern("once"),
1904 push_macro: interner.intern("push_macro"),
1905 pop_macro: interner.intern("pop_macro"),
1906 pragma_op: interner.intern("_Pragma"),
1907 has: HasOps::new(interner),
1908 }
1909 }
1910}
1911
1912#[cfg(test)]
1913mod tests {
1914 use rucc_diag::{Severity, SourceMap};
1915 use rucc_session::{MemoryFileSystem, SearchPath};
1916
1917 use super::*;
1918 use rucc_session::Std;
1919
1920 use crate::predef::Timestamp;
1921
1922 struct Run {
1927 interner: Interner,
1928 sources: SourceMap,
1929 fs: MemoryFileSystem,
1930 search: SearchPath,
1931 pp: Preprocessor,
1932 }
1933
1934 impl Run {
1935 fn new() -> Run {
1936 Run {
1937 interner: Interner::new(),
1938 sources: SourceMap::new(),
1939 fs: MemoryFileSystem::new(),
1940 search: SearchPath::new(),
1941 pp: Preprocessor::new(),
1942 }
1943 }
1944
1945 fn file(&mut self, path: &str, contents: &str) {
1947 self.fs.insert(path, contents.as_bytes().to_vec());
1948 }
1949
1950 fn bytes(&mut self, path: &str, contents: &[u8]) {
1953 self.fs.insert(path, contents.to_vec());
1954 }
1955
1956 fn dir(&mut self, path: &str) {
1958 self.search.push_bracket(path);
1959 }
1960
1961 fn predefine(&mut self, triple: &str, opts: &Predef) {
1963 let target = TargetInfo::new(triple.parse().expect("a supported triple"));
1964 let mut cx =
1965 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1966 self.pp.predefine(&target, opts, &mut cx).expect("the map has room");
1967 }
1968
1969 fn go(&mut self, src: &str) -> String {
1971 self.go_named("/main.c", src)
1972 }
1973
1974 fn raw(&mut self, src: &str) -> Vec<Tok> {
1976 let file = self.sources.add("/main.c", src.as_bytes().to_vec()).expect("room");
1977 let mut cx =
1978 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1979 self.pp.run(file, &mut cx)
1980 }
1981
1982 fn preinclude(&mut self, files: &[Preinclude]) -> String {
1984 let mut out = Vec::new();
1985 {
1986 let mut cx =
1987 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1988 self.pp.preinclude(files, &mut out, &mut cx).expect("the map has room");
1989 }
1990 self.spell(&out)
1991 }
1992
1993 fn go_named(&mut self, path: &str, src: &str) -> String {
1995 let file = self.sources.add(path, src.as_bytes().to_vec()).expect("the map has room");
1996 let out = {
1997 let mut cx =
1998 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1999 self.pp.run(file, &mut cx)
2000 };
2001 self.spell(&out)
2002 }
2003
2004 fn spell(&self, out: &[Tok]) -> String {
2006 let mut text = String::new();
2007 for (at, tok) in out.iter().enumerate() {
2008 let spaced = tok.flags.has(TokenFlags::LEADING_SPACE)
2009 || tok.flags.has(TokenFlags::START_OF_LINE);
2010 if at > 0 && spaced {
2011 text.push(' ');
2012 }
2013 match tok.kind {
2014 PpTokenKind::Punct(p) => text.push_str(p.as_str()),
2015 _ => text.push_str(
2016 self.interner.resolve(tok.value.expect("every non-punctuator interns")),
2017 ),
2018 }
2019 }
2020 text
2021 }
2022
2023 fn files(&self) -> usize {
2027 self.sources.files().len()
2028 }
2029
2030 fn messages(&mut self) -> Vec<String> {
2031 self.pp.take_diagnostics().into_iter().map(|d| d.message).collect()
2032 }
2033
2034 fn severities(&mut self) -> Vec<Severity> {
2035 self.pp.diagnostics().iter().map(|d| d.severity).collect()
2036 }
2037 }
2038
2039 fn clean(src: &str) -> String {
2040 let mut run = Run::new();
2041 let text = run.go(src);
2042 assert!(run.messages().is_empty(), "expected no diagnostics from {src:?}");
2043 text
2044 }
2045
2046 #[test]
2047 fn a_taken_branch_is_kept_and_the_other_is_not() {
2048 assert_eq!(clean("#if 1\nyes\n#else\nno\n#endif\n"), "yes");
2049 assert_eq!(clean("#if 0\nyes\n#else\nno\n#endif\n"), "no");
2050 }
2051
2052 #[test]
2053 fn ifdef_and_ifndef_ask_the_macro_table() {
2054 assert_eq!(clean("#define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
2055 assert_eq!(clean("#ifdef F\nyes\n#endif\n"), "");
2056 assert_eq!(clean("#ifndef F\nyes\n#endif\n"), "yes");
2057 assert_eq!(clean("#define F 1\n#if 0\na\n#elifdef F\nb\n#endif\n"), "b");
2059 assert_eq!(clean("#if 0\na\n#elifndef F\nb\n#endif\n"), "b");
2060 }
2061
2062 #[test]
2063 fn only_the_first_true_branch_of_a_chain_is_taken() {
2064 assert_eq!(clean("#if 0\na\n#elif 1\nb\n#elif 1\nc\n#else\nd\n#endif\n"), "b");
2065 assert_eq!(clean("#if 0\na\n#elif 0\nb\n#else\nc\n#endif\n"), "c");
2066 }
2067
2068 #[test]
2069 fn a_branch_after_one_that_was_taken_is_not_evaluated() {
2070 assert_eq!(clean("#if 1\na\n#elif 1/0\nb\n#endif\n"), "a");
2073 }
2074
2075 #[test]
2076 fn a_skipped_region_is_not_read_for_anything_but_nesting() {
2077 let src = "#if 0\nthis is not C at all\n#frobnicate\n#define\n#if 1\ninner\n#endif\n#endif\nafter\n";
2079 assert_eq!(clean(src), "after");
2080 }
2081
2082 #[test]
2083 fn nesting_inside_a_dead_branch_stays_balanced() {
2084 let src = "#if 0\n#ifdef X\na\n#else\nb\n#endif\n#else\nc\n#endif\n";
2085 assert_eq!(clean(src), "c");
2086 }
2087
2088 #[test]
2089 fn defined_works_in_both_spellings_and_before_expansion() {
2090 assert_eq!(clean("#define F 0\n#if defined F\nyes\n#endif\n"), "yes");
2091 assert_eq!(clean("#define F 0\n#if defined(F)\nyes\n#endif\n"), "yes");
2092 assert_eq!(clean("#if defined(F)\nyes\n#endif\n"), "");
2093 assert_eq!(clean("#define F 0\n#if defined F && !F\nyes\n#endif\n"), "yes");
2096 }
2097
2098 #[test]
2099 fn an_identifier_that_survived_expansion_is_zero() {
2100 assert_eq!(clean("#if NOT_DEFINED_ANYWHERE\nyes\n#else\nno\n#endif\n"), "no");
2101 assert_eq!(clean("#if !NOT_DEFINED_ANYWHERE\nyes\n#endif\n"), "yes");
2102 }
2103
2104 #[test]
2105 fn short_circuiting_keeps_a_guarded_expression_safe() {
2106 assert_eq!(clean("#if defined(F) && 1/F\nyes\n#else\nno\n#endif\n"), "no");
2109 assert_eq!(clean("#if 1 ? 2 : 1/0\nyes\n#endif\n"), "yes");
2110 }
2111
2112 #[test]
2113 fn the_operators_have_the_precedence_they_do_in_c() {
2114 assert_eq!(clean("#if 1 + 2 * 3 == 7\nyes\n#endif\n"), "yes");
2115 assert_eq!(clean("#if (1 + 2) * 3 == 9\nyes\n#endif\n"), "yes");
2116 assert_eq!(clean("#if 1 << 4 == 16\nyes\n#endif\n"), "yes");
2117 assert_eq!(clean("#if -8 / 3 == -2\nyes\n#endif\n"), "yes");
2118 assert_eq!(clean("#if (0xff & 0x0f) == 15\nyes\n#endif\n"), "yes");
2119 }
2120
2121 #[test]
2122 fn an_unsigned_operand_makes_the_whole_comparison_unsigned() {
2123 assert_eq!(clean("#if -1 < 0u\nyes\n#else\nno\n#endif\n"), "no");
2127 assert_eq!(clean("#if -1 < 0\nyes\n#else\nno\n#endif\n"), "yes");
2128 }
2129
2130 #[test]
2131 fn character_constants_evaluate() {
2132 assert_eq!(clean("#if 'A' == 65\nyes\n#endif\n"), "yes");
2133 assert_eq!(clean("#if '\\n' == 10\nyes\n#endif\n"), "yes");
2134 }
2135
2136 #[test]
2137 fn a_macro_is_expanded_before_the_expression_is_evaluated() {
2138 assert_eq!(clean("#define V 3\n#if V > 2\nyes\n#endif\n"), "yes");
2139 assert_eq!(clean("#define M(a) ((a) * 2)\n#if M(3) == 6\nyes\n#endif\n"), "yes");
2140 }
2141
2142 #[test]
2143 fn an_invocation_may_span_lines_within_a_run_of_text() {
2144 assert_eq!(clean("#define M(a, b) a + b\nM(1,\n2)\n"), "1 + 2");
2145 }
2146
2147 #[test]
2148 fn undef_removes_a_definition() {
2149 assert_eq!(clean("#define F 1\n#undef F\n#ifdef F\nyes\n#else\nno\n#endif\n"), "no");
2150 assert_eq!(clean("#undef NEVER_DEFINED\nok\n"), "ok");
2153 }
2154
2155 #[test]
2156 fn some_names_cannot_be_undefined() {
2157 let mut run = Run::new();
2158 run.go("#undef defined\n");
2159 assert_eq!(run.messages(), vec!["`defined` cannot be undefined".to_owned()]);
2160 }
2161
2162 #[test]
2163 fn error_reports_the_rest_of_the_line() {
2164 let mut run = Run::new();
2165 run.go("#if 0\n#error not this one\n#else\n#error unsupported target\n#endif\n");
2166 assert_eq!(run.messages(), vec!["unsupported target".to_owned()]);
2167 }
2168
2169 #[test]
2170 fn warning_is_a_warning() {
2171 let mut run = Run::new();
2172 run.go("#warning this is fine\n");
2173 assert_eq!(run.severities(), vec![Severity::Warning]);
2174 assert_eq!(run.messages(), vec!["this is fine".to_owned()]);
2175 }
2176
2177 #[test]
2178 fn an_unterminated_conditional_is_reported() {
2179 let mut run = Run::new();
2180 assert_eq!(run.go("#if 1\nyes\n"), "yes");
2181 assert_eq!(run.messages(), vec!["unterminated `#if`".to_owned()]);
2182 }
2183
2184 #[test]
2185 fn a_conditional_without_an_if_is_reported() {
2186 let mut run = Run::new();
2187 run.go("#endif\n");
2188 assert_eq!(run.messages(), vec!["`#endif` without `#if`".to_owned()]);
2189
2190 let mut run = Run::new();
2191 run.go("#if 1\n#else\n#else\n#endif\n");
2192 assert_eq!(run.messages(), vec!["a second `#else`".to_owned()]);
2193
2194 let mut run = Run::new();
2195 run.go("#if 1\n#else\n#elif 1\n#endif\n");
2196 assert_eq!(run.messages(), vec!["`#elif` after `#else`".to_owned()]);
2197 }
2198
2199 #[test]
2200 fn tokens_after_endif_are_a_warning_rather_than_an_error() {
2201 let mut run = Run::new();
2204 assert_eq!(run.go("#if 1\nyes\n#endif FOO\n"), "yes");
2205 assert_eq!(run.severities(), vec![Severity::Warning]);
2206 assert_eq!(run.messages(), vec!["extra tokens after `#endif`".to_owned()]);
2207 }
2208
2209 #[test]
2210 fn the_null_directive_does_nothing() {
2211 assert_eq!(clean("#\na\n#\nb\n"), "a b");
2212 }
2213
2214 #[test]
2215 fn an_unknown_directive_is_an_error_when_the_region_is_live() {
2216 let mut run = Run::new();
2217 run.go("#frobnicate\n");
2218 assert_eq!(run.messages(), vec!["invalid preprocessing directive".to_owned()]);
2219 }
2220
2221 #[test]
2222 fn line_is_recorded_for_the_source_map() {
2223 let mut run = Run::new();
2224 run.go("#line 42 \"other.c\"\n");
2225 assert!(run.messages().is_empty());
2226 let recorded = run.pp.line_directives();
2227 assert_eq!(recorded.len(), 1);
2228 assert_eq!(recorded[0].line, 42);
2229 let file = recorded[0].file.expect("a file name was given");
2230 assert_eq!(run.interner.resolve(file), "\"other.c\"");
2231 }
2232
2233 #[test]
2234 fn line_moves_what_line_and_file_the_lines_after_it_are_on() {
2235 let mut run = Run::new();
2236 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2237 assert_eq!(run.go("#line 1000\n__LINE__ __FILE__\n__LINE__\n"), "1000 \"/main.c\" 1001");
2238 }
2239
2240 #[test]
2241 fn a_line_marker_moves_the_lines_after_it_the_way_line_does() {
2242 let mut run = Run::new();
2243 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2244 assert_eq!(run.go("# 200 \"xyz\"\n__FILE__ __LINE__\n"), "\"xyz\" 200");
2245 }
2246
2247 #[test]
2248 fn a_line_marker_with_no_name_leaves_the_name_alone() {
2249 let mut run = Run::new();
2250 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2251 assert_eq!(run.go("# 20\n__FILE__ __LINE__\n"), "\"/main.c\" 20");
2252 }
2253
2254 #[test]
2255 fn a_line_marker_may_say_line_zero() {
2256 let mut run = Run::new();
2259 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2260 assert_eq!(run.go("# 0 \"xyz\"\n__LINE__\n"), "0");
2261 }
2262
2263 #[test]
2264 fn entering_and_returning_are_a_nesting_the_marker_flags_keep() {
2265 let mut run = Run::new();
2266 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2267 let text =
2268 run.go("# 200 \"xyz\" 1\n__FILE__\n# 5 \"/main.c\" 2\n__FILE__ __LINE__\n# 9 3 4\n");
2269 assert_eq!(text, "\"xyz\" \"/main.c\" 5");
2270 assert!(run.messages().is_empty());
2271 }
2272
2273 #[test]
2274 fn returning_to_a_file_nothing_was_ever_in_is_ignored() {
2275 let mut run = Run::new();
2276 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2277 assert_eq!(run.go("# 200 \"xyz\" 2 3\n__FILE__ __LINE__\n"), "\"/main.c\" 2");
2278 assert_eq!(
2279 run.messages(),
2280 vec!["file `xyz` linemarker ignored due to incorrect nesting".to_owned()]
2281 );
2282 }
2283
2284 #[test]
2285 fn a_flag_that_is_not_one_of_the_four_is_an_error() {
2286 let mut run = Run::new();
2287 run.go("# 20 \"a\" 7\n");
2288 assert_eq!(run.messages(), vec!["invalid flag `7` in line directive".to_owned()]);
2289 }
2290
2291 #[test]
2292 fn a_hash_and_something_that_is_not_a_line_number_is_still_an_unknown_directive() {
2293 let mut run = Run::new();
2295 run.go("# 1.5 \"a\"\n");
2296 assert_eq!(run.messages(), vec!["invalid preprocessing directive".to_owned()]);
2297 }
2298
2299 #[test]
2300 fn a_name_on_the_directive_is_the_name_from_there_on() {
2301 let mut run = Run::new();
2302 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2303 assert_eq!(run.go("#line 7 \"gen.y\"\n__FILE__ __LINE__\n"), "\"gen.y\" 7");
2304 }
2305
2306 #[test]
2307 fn a_directive_with_no_name_keeps_the_one_already_in_force() {
2308 let mut run = Run::new();
2309 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2310 assert_eq!(run.go("#line 7 \"gen.y\"\n#line 20\n__FILE__ __LINE__\n"), "\"gen.y\" 20");
2311 }
2312
2313 #[test]
2314 fn the_number_is_expanded_first_because_line_plus_one_is_real_code() {
2315 let mut run = Run::new();
2316 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2317 assert_eq!(run.go("#define WHERE 300\n#line WHERE\n__LINE__\n"), "300");
2318 }
2319
2320 #[test]
2321 fn a_directive_in_a_header_does_not_move_the_file_that_included_it() {
2322 let mut run = Run::new();
2323 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2324 run.file("/h.h", "#line 500\n__LINE__\n");
2325 run.dir("/");
2326 assert_eq!(run.go("#include <h.h>\n__LINE__\n"), "500 2");
2327 }
2328
2329 #[test]
2330 fn extra_tokens_after_the_file_name_are_a_warning_and_not_an_error() {
2331 let mut run = Run::new();
2332 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2333 assert_eq!(run.go("#line 7 \"gen.y\" and more\n__LINE__\n"), "7");
2334 assert_eq!(run.messages(), vec!["extra tokens after `#line`".to_owned()]);
2335 }
2336
2337 #[test]
2338 fn a_line_number_out_of_range_is_refused() {
2339 let mut run = Run::new();
2340 run.go("#line 0\n");
2341 assert_eq!(run.messages(), vec!["`#line` number is out of range".to_owned()]);
2342
2343 let mut run = Run::new();
2344 run.go("#line notanumber\n");
2345 assert_eq!(run.messages(), vec!["`#line` needs a decimal line number".to_owned()]);
2346 }
2347
2348 #[test]
2349 fn a_pragma_passes_through_unchanged() {
2350 assert_eq!(clean("#pragma pack(1)\nint x;\n"), "#pragma pack(1) int x;");
2351 }
2352
2353 #[test]
2358 fn the_space_between_the_hash_and_the_word_comes_off_a_pragma_that_is_indented() {
2359 assert_eq!(clean("#if 1\n# pragma pack(1)\n#endif\n"), "#pragma pack(1)");
2360 assert_eq!(clean("# pragma pack( 1 )\n"), "#pragma pack( 1 )", "the rest is kept");
2361 }
2362
2363 #[test]
2364 fn the_pragma_operator_becomes_a_pragma() {
2365 assert_eq!(
2366 clean("_Pragma(\"GCC visibility push(default)\")\nint x;\n"),
2367 "#pragma GCC visibility push(default) int x;"
2368 );
2369 }
2370
2371 #[test]
2372 fn the_pragma_operator_works_from_inside_a_macro() {
2373 let src = "#define PUSH _Pragma(\"pack(push)\")\nPUSH\nint x;\n";
2376 assert_eq!(clean(src), "#pragma pack(push) int x;");
2377 }
2378
2379 #[test]
2383 fn what_follows_a_pragma_operator_starts_a_line() {
2384 let mut run = Run::new();
2385 let out = run.raw("int x; _Pragma(\"pack(1)\") int y;\n");
2386 let starts: Vec<_> =
2387 out.iter().map(|tok| tok.flags.has(TokenFlags::START_OF_LINE)).collect();
2388 assert_eq!(
2391 starts,
2392 vec![true, false, false, true, false, false, false, false, false, true, false, false]
2393 );
2394 }
2395
2396 #[test]
2397 fn a_pragma_operator_that_is_not_given_a_string_is_reported() {
2398 let mut run = Run::new();
2399 run.go("_Pragma(x)\n");
2400 assert_eq!(run.messages(), vec!["`_Pragma` takes a single string literal".to_owned()]);
2401 }
2402
2403 #[test]
2404 fn an_include_reads_the_file_it_names() {
2405 let mut run = Run::new();
2406 run.file("/dir/one.h", "int from_the_header;\n");
2407 run.dir("/dir");
2408 assert_eq!(run.go("#include <one.h>\nint after;\n"), "int from_the_header; int after;");
2409 assert!(run.messages().is_empty());
2410 }
2411
2412 #[test]
2413 fn a_quoted_include_looks_next_to_the_including_file_first() {
2414 let mut run = Run::new();
2415 run.file("/local.h", "beside\n");
2416 run.file("/dir/local.h", "on the path\n");
2417 run.dir("/dir");
2418 assert_eq!(run.go("#include \"local.h\"\n"), "beside");
2419 assert!(run.messages().is_empty());
2420 }
2421
2422 #[test]
2423 fn an_angled_include_does_not_look_next_to_the_including_file() {
2424 let mut run = Run::new();
2425 run.file("/local.h", "beside\n");
2426 run.file("/dir/local.h", "on the path\n");
2427 run.dir("/dir");
2428 assert_eq!(run.go("#include <local.h>\n"), "on the path");
2429 }
2430
2431 #[test]
2432 fn a_macro_defined_in_a_header_is_visible_after_the_include() {
2433 let mut run = Run::new();
2434 run.file("/dir/defs.h", "#define N 42\n");
2435 run.dir("/dir");
2436 assert_eq!(run.go("#include <defs.h>\nint a = N;\n"), "int a = 42;");
2437 assert!(run.messages().is_empty());
2438 }
2439
2440 #[test]
2441 fn an_include_guard_keeps_the_second_read_empty() {
2442 let mut run = Run::new();
2443 run.file("/dir/g.h", "#ifndef G\n#define G\nonce\n#endif\n");
2444 run.dir("/dir");
2445 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
2446 assert!(run.messages().is_empty());
2447 assert_eq!(run.files(), 2, "the second include is not opened at all");
2448 }
2449
2450 fn named(name: &str, macros_only: bool) -> Preinclude {
2451 Preinclude { name: name.to_owned(), macros_only }
2452 }
2453
2454 #[test]
2455 fn a_command_line_include_contributes_its_text_and_an_imacros_contributes_none() {
2456 let mut run = Run::new();
2457 run.file("i.h", "from_include\n#define I 1\n");
2458 run.file("m.h", "from_macros\n#define M 1\n");
2459 assert_eq!(run.preinclude(&[named("i.h", false), named("m.h", true)]), "from_include");
2460 assert_eq!(run.go("I M\n"), "1 1");
2462 }
2463
2464 #[test]
2465 fn every_imacros_runs_before_every_include_whatever_order_the_command_line_was_in() {
2466 for files in
2469 [[named("i.h", false), named("m.h", true)], [named("m.h", true), named("i.h", false)]]
2470 {
2471 let mut run = Run::new();
2472 run.file("i.h", "#ifdef M\nsaw_it\n#else\nmissed_it\n#endif\n");
2473 run.file("m.h", "#define M 1\n");
2474 assert_eq!(run.preinclude(&files), "saw_it");
2475 }
2476 }
2477
2478 #[test]
2479 fn a_header_read_for_its_macros_is_not_read_again_by_an_include_that_its_guard_covers() {
2480 let mut run = Run::new();
2483 run.file("/dir/g.h", "#ifndef G\n#define G\ndeclarations\n#endif\n");
2484 run.dir("/dir");
2485 assert_eq!(run.preinclude(&[named("/dir/g.h", true)]), "");
2486 assert_eq!(run.go("#include <g.h>\n"), "");
2487 assert!(run.messages().is_empty());
2488 }
2489
2490 #[test]
2491 fn a_command_line_include_is_a_dependency_and_is_named_before_the_headers_it_reads() {
2492 let mut run = Run::new();
2493 run.file("i.h", "#include \"deep.h\"\n");
2494 run.file("deep.h", "\n");
2495 run.file("m.h", "\n");
2496 run.preinclude(&[named("i.h", false), named("m.h", true)]);
2497 let names: Vec<String> =
2498 run.pp.dependencies().iter().map(|d| d.path.to_string_lossy().into_owned()).collect();
2499 let names: Vec<String> = names.iter().map(|n| n.replace('\\', "/")).collect();
2500 assert_eq!(names, ["m.h", "i.h", "deep.h"]);
2501 }
2502
2503 #[test]
2504 fn a_prerequisite_is_spelled_without_the_dot_the_search_path_was_written_with() {
2505 let mut run = Run::new();
2509 run.file("d/f.h", "\n");
2510 run.dir("./d");
2511 run.go("#include <f.h>\n");
2512 let names: Vec<String> =
2513 run.pp.dependencies().iter().map(|d| d.path.to_string_lossy().into_owned()).collect();
2514 assert_eq!(names.iter().map(|n| n.replace('\\', "/")).collect::<Vec<_>>(), ["d/f.h"]);
2515 }
2516
2517 #[test]
2518 fn a_command_line_include_that_is_nowhere_is_reported_against_the_flag_that_named_it() {
2519 let mut run = Run::new();
2520 assert_eq!(run.preinclude(&[named("nope.h", false)]), "");
2521 assert_eq!(run.messages(), ["`nope.h` file not found"]);
2522 }
2523
2524 #[test]
2525 fn the_other_spelling_of_a_guard_is_recognised_too() {
2526 for guard in ["#if !defined(G)", "#if !defined G"] {
2527 let mut run = Run::new();
2528 run.file("/dir/g.h", &format!("{guard}\n#define G\nonce\n#endif\n"));
2529 run.dir("/dir");
2530 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
2531 assert_eq!(run.files(), 2, "{guard} should be a guard");
2532 }
2533 }
2534
2535 #[test]
2536 fn a_conditional_that_is_not_a_guard_does_not_skip_anything() {
2537 let mut run = Run::new();
2540 run.file("/dir/g.h", "#ifndef G\ntwice\n#endif\n");
2541 run.dir("/dir");
2542 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "twice twice");
2543 assert_eq!(run.files(), 3);
2544 }
2545
2546 #[test]
2547 fn a_token_outside_the_guard_stops_it_being_a_guard() {
2548 let mut run = Run::new();
2549 run.file("/dir/g.h", "#ifndef G\n#define G\n#endif\nalways\n");
2550 run.dir("/dir");
2551 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "always always");
2552 assert_eq!(run.files(), 3);
2553 }
2554
2555 #[test]
2556 fn pragma_once_skips_the_second_read_and_does_not_reach_the_output() {
2557 let mut run = Run::new();
2558 run.file("/dir/o.h", "#pragma once\nonce\n");
2559 run.dir("/dir");
2560 assert_eq!(run.go("#include <o.h>\n#include <o.h>\n"), "once");
2561 assert!(run.messages().is_empty());
2562 assert_eq!(run.files(), 2);
2563 }
2564
2565 #[test]
2566 fn pragma_once_in_the_main_file_is_a_warning_and_is_still_applied() {
2567 let mut run = Run::new();
2571 let src = "#pragma once\n#include <s.c>\nbody\n";
2572 run.file("/dir/s.c", src);
2573 run.dir("/dir");
2574 assert_eq!(run.go_named("/dir/s.c", src), "body");
2575 assert_eq!(run.severities(), vec![Severity::Warning]);
2576 assert_eq!(run.messages(), vec!["`#pragma once` in the main file".to_owned()]);
2577 assert_eq!(run.files(), 1);
2578 }
2579
2580 #[test]
2581 fn pragma_once_holds_across_two_spellings_of_the_one_path() {
2582 let mut run = Run::new();
2585 run.file("dir/s.c", "#pragma once\nbody\n");
2586 run.dir(".");
2587 assert_eq!(run.go("#include <dir/s.c>\n#include <dir/s.c>\n"), "body");
2588 assert!(run.messages().is_empty());
2589 assert_eq!(run.files(), 2);
2590 }
2591
2592 #[test]
2593 fn any_other_pragma_still_passes_through() {
2594 assert_eq!(clean("#pragma once_upon_a_time\n"), "#pragma once_upon_a_time");
2595 }
2596
2597 #[test]
2600 fn push_macro_and_pop_macro_put_a_definition_aside_and_bring_it_back() {
2601 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";
2602 assert_eq!(clean(src), "a 2 b 1");
2603 }
2604
2605 #[test]
2606 fn a_name_with_no_definition_pushes_and_pops_the_absence() {
2607 let src = "#pragma push_macro(\"X\")\n#define X 1\na X\n#pragma pop_macro(\"X\")\nb X\n";
2610 assert_eq!(clean(src), "a 1 b X");
2611 }
2612
2613 #[test]
2614 fn the_pushes_nest() {
2615 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";
2616 assert_eq!(clean(src), "a 3 b 2 c 1");
2617 }
2618
2619 #[test]
2620 fn a_pop_with_nothing_pushed_says_nothing() {
2621 assert_eq!(clean("#define X 1\n#pragma pop_macro(\"X\")\nX\n"), "1");
2624 assert_eq!(clean("#pragma pop_macro(\"Never\")\nx\n"), "x");
2625 }
2626
2627 #[test]
2628 fn the_pragma_operator_spelling_works_and_takes_effect_where_it_is_written() {
2629 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";
2634 assert_eq!(clean(src), "a 2 b 1");
2635 }
2636
2637 #[test]
2638 fn the_gcc_spelling_is_not_one_of_these_and_passes_through() {
2639 let src = "#define X 1\n#pragma GCC push_macro(\"X\")\n#undef X\n#define X 2\nX\n";
2643 assert_eq!(clean(src), "#pragma GCC push_macro(\"X\") 2");
2644 }
2645
2646 #[test]
2647 fn a_push_macro_that_is_not_the_shape_is_an_error() {
2648 for src in ["#pragma push_macro\n", "#pragma push_macro(X)\n", "#pragma pop_macro()\n"] {
2649 let mut run = Run::new();
2650 run.go(src);
2651 let word = if src.contains("push") { "push" } else { "pop" };
2652 assert_eq!(
2653 run.messages(),
2654 vec![format!("invalid `#pragma {word}_macro` directive")],
2655 "from {src:?}"
2656 );
2657 }
2658 }
2659
2660 #[test]
2661 fn a_string_that_does_not_spell_one_identifier_names_no_macro() {
2662 assert_eq!(clean("#pragma push_macro(\"a b\")\nx\n"), "x");
2665 assert_eq!(clean("#pragma push_macro(\"2\")\nx\n"), "x");
2666 }
2667
2668 #[test]
2669 fn what_follows_the_closing_parenthesis_is_the_usual_warning() {
2670 let mut run = Run::new();
2671 assert_eq!(run.go("#define X 1\n#pragma push_macro(\"X\") junk\nX\n"), "1");
2672 assert_eq!(run.severities(), vec![Severity::Warning]);
2673 assert_eq!(run.messages(), vec!["extra tokens after `#pragma`".to_owned()]);
2674 }
2675
2676 #[test]
2677 fn has_include_answers_from_the_search_path() {
2678 let mut run = Run::new();
2679 run.file("/dir/there.h", "");
2680 run.dir("/dir");
2681 let src = "#if __has_include(<there.h>)\nyes\n#endif\n\
2682 #if __has_include(<gone.h>)\nno\n#endif\n";
2683 assert_eq!(run.go(src), "yes");
2684 assert!(run.messages().is_empty(), "a header that is not there is an answer, not an error");
2685 }
2686
2687 #[test]
2688 fn has_include_asks_the_question_the_include_on_the_same_line_would() {
2689 let mut run = Run::new();
2693 run.file("/beside.h", "");
2694 let src = "#if __has_include(\"beside.h\")\nquoted\n#endif\n\
2695 #if __has_include(<beside.h>)\nangled\n#endif\n";
2696 assert_eq!(run.go(src), "quoted");
2697 }
2698
2699 #[test]
2700 fn has_include_next_starts_where_include_next_would() {
2701 let mut run = Run::new();
2702 run.file("/a/both.h", "#if __has_include_next(<both.h>)\nmore\n#endif\n");
2703 run.file("/b/both.h", "last\n");
2704 run.file("/a/only.h", "#if __has_include_next(<only.h>)\nmore\n#endif\n");
2705 run.dir("/a");
2706 run.dir("/b");
2707 assert_eq!(run.go("#include <both.h>\n"), "more");
2708 assert_eq!(run.go("#include <only.h>\n"), "", "there is nothing after /a to find it in");
2709 }
2710
2711 #[test]
2712 fn the_operand_of_has_include_is_not_macro_expanded() {
2713 let mut run = Run::new();
2716 run.file("/dir/linux/version.h", "");
2717 run.dir("/dir");
2718 let src = "#define linux 1\n#if __has_include(<linux/version.h>)\nyes\n#endif\n";
2719 assert_eq!(run.go(src), "yes");
2720 }
2721
2722 #[test]
2723 fn a_macro_may_expand_to_a_has_include() {
2724 let mut run = Run::new();
2726 run.file("/dir/there.h", "");
2727 run.dir("/dir");
2728 let src = "#define HAVE __has_include(<there.h>)\n#if HAVE\nyes\n#endif\n";
2729 assert_eq!(run.go(src), "yes");
2730 }
2731
2732 #[test]
2733 fn defined_says_the_has_operators_are_there() {
2734 let src = "#if defined(__has_include) && defined __has_builtin\nyes\n#endif\n";
2737 assert_eq!(clean(src), "yes");
2738 assert_eq!(clean("#ifdef __has_attribute\nyes\n#endif\n"), "yes");
2739 }
2740
2741 #[test]
2742 fn has_attribute_answers_out_of_the_matrix() {
2743 assert_eq!(clean("#if __has_attribute(packed)\nyes\n#endif\n"), "yes");
2747 assert_eq!(clean("#if __has_attribute(cold)\nyes\n#endif\n"), "");
2748 assert_eq!(clean("#if __has_attribute(no_such_attribute)\nyes\n#endif\n"), "");
2749 assert_eq!(clean("#if !__has_attribute(cold)\nno\n#endif\n"), "no");
2750 }
2751
2752 #[test]
2753 fn the_scoped_spelling_of_an_attribute_is_the_same_question() {
2754 assert_eq!(clean("#if __has_c_attribute(gnu::packed)\nyes\n#endif\n"), "");
2760 assert_eq!(clean("#if __has_c_attribute(deprecated)\nyes\n#endif\n"), "");
2761 }
2762
2763 #[test]
2764 fn has_builtin_answers_no_until_the_builtin_is_real() {
2765 assert_eq!(clean("#if __has_builtin(__builtin_expect)\nyes\n#endif\n"), "yes");
2766 assert_eq!(clean("#if __has_builtin(__builtin_clz)\nyes\n#endif\n"), "yes");
2767 assert_eq!(clean("#if __has_builtin(__builtin_alloca)\nyes\n#endif\n"), "");
2768 assert_eq!(clean("#if __has_builtin(__builtin_nonesuch)\nyes\n#endif\n"), "");
2769 }
2770
2771 #[test]
2772 fn has_feature_and_has_extension_read_the_same_table() {
2773 assert_eq!(clean("#if __has_feature(pragma_once)\nyes\n#endif\n"), "yes");
2776 assert_eq!(clean("#if __has_extension(pragma_once)\nyes\n#endif\n"), "yes");
2777 assert_eq!(clean("#if __has_extension(include_next)\nyes\n#endif\n"), "yes");
2778 assert_eq!(clean("#if __has_feature(include_next)\nyes\n#endif\n"), "");
2779 assert_eq!(clean("#if __has_feature(statement_expressions)\nyes\n#endif\n"), "");
2780 }
2781
2782 #[test]
2783 fn building_module_is_always_no_and_is_recognised_so_that_the_line_parses() {
2784 assert_eq!(clean("#if __building_module(m)\nyes\n#endif\n"), "");
2788 assert_eq!(clean("#if !__building_module(m)\nyes\n#endif\n"), "yes");
2789 assert_eq!(
2790 clean(
2791 "#if !defined(offsetof) || (__has_feature(modules) && !__building_module(x))\nyes\n#endif\n"
2792 ),
2793 "yes"
2794 );
2795 assert_eq!(clean("#ifdef __building_module\nyes\n#endif\n"), "yes");
2797 assert_eq!(clean("#if defined(__building_module)\nyes\n#endif\n"), "yes");
2798 }
2799
2800 #[test]
2801 fn a_has_operator_without_an_operand_is_reported() {
2802 let mut run = Run::new();
2803 run.go("#if __has_include\nyes\n#endif\n");
2804 assert_eq!(run.messages(), ["expected `(` after `__has_include`"]);
2805 let mut run = Run::new();
2806 run.go("#if __has_include(1)\nyes\n#endif\n");
2807 assert_eq!(run.messages(), ["expected a file name in `<>` or `\"\"`"]);
2808 let mut run = Run::new();
2809 run.go("#if __has_attribute(\"packed\")\nyes\n#endif\n");
2810 assert_eq!(run.messages(), ["expected an identifier as the operand of `__has_attribute`"]);
2811 }
2812
2813 #[test]
2814 fn the_has_operators_answer_in_ordinary_text_too() {
2815 assert_eq!(clean("f __has_feature(pragma_once)\n"), "f 1");
2819 assert_eq!(clean("b __has_builtin(__builtin_expect)\n"), "b 1");
2820 assert_eq!(clean("a __has_attribute(packed)\n"), "a 1");
2821 assert_eq!(clean("c __has_c_attribute(deprecated)\n"), "c 0");
2822 assert_eq!(clean("m __building_module(foo)\n"), "m 0");
2823 }
2824
2825 #[test]
2826 fn a_macro_that_expands_to_a_has_operator_is_answered_where_it_is_used() {
2827 assert_eq!(clean("#define HAVE __has_feature(pragma_once)\nx HAVE\n"), "x 1");
2830 assert_eq!(clean("#define HAVE(x) __has_attribute(x)\ny HAVE(packed)\n"), "y 1");
2831 }
2832
2833 #[test]
2834 fn a_has_operator_in_text_still_needs_its_operand() {
2835 let mut run = Run::new();
2836 run.go("tail __has_attribute;\n");
2837 assert_eq!(run.messages(), ["expected `(` after `__has_attribute`"]);
2838 }
2839
2840 #[test]
2841 fn the_header_operators_are_refused_in_ordinary_text() {
2842 let mut run = Run::new();
2845 run.file("/dir/there.h", "");
2846 run.dir("/dir");
2847 run.go("a __has_include(<there.h>)\n");
2848 assert_eq!(run.messages(), ["`__has_include` used outside of a preprocessing directive"]);
2849 let mut run = Run::new();
2850 run.go("b __has_include_next(\"x.h\")\n");
2851 assert_eq!(
2852 run.messages(),
2853 ["`__has_include_next` used outside of a preprocessing directive"]
2854 );
2855 }
2856
2857 #[test]
2858 fn the_predefined_set_is_visible_to_the_source_file() {
2859 let mut run = Run::new();
2860 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2861 let src = "#if defined(__x86_64__) && defined(__linux__) && __SIZEOF_LONG__ == 8\n\
2862 yes\n#endif\n";
2863 assert_eq!(run.go(src), "yes");
2864 assert!(run.messages().is_empty());
2865 }
2866
2867 #[test]
2868 fn the_predefined_set_follows_the_target_and_not_the_host() {
2869 let mut run = Run::new();
2870 run.predefine("aarch64-unknown-linux-gnu", &Predef::new());
2871 assert_eq!(
2872 run.go("#ifdef __x86_64__\nno\n#endif\n#ifdef __aarch64__\nyes\n#endif\n"),
2873 "yes"
2874 );
2875 }
2876
2877 #[test]
2878 fn a_predefined_macro_expands_where_it_is_used() {
2879 let mut run = Run::new();
2880 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2881 assert_eq!(run.go("__SIZE_TYPE__ n;\n"), "long unsigned int n;");
2882 }
2883
2884 #[test]
2885 fn a_command_line_define_is_a_definition_like_any_other() {
2886 let mut opts = Predef::new();
2887 opts.defines = vec!["FOO".to_owned(), "BAR=3".to_owned()];
2888 opts.undefines = vec!["__linux__".to_owned()];
2889 let mut run = Run::new();
2890 run.predefine("x86_64-unknown-linux-gnu", &opts);
2891 let src = "#if FOO && BAR == 3 && !defined(__linux__)\nyes\n#endif\n";
2892 assert_eq!(run.go(src), "yes");
2893 assert!(run.messages().is_empty());
2894 }
2895
2896 #[test]
2897 fn the_predefined_set_produces_no_tokens_of_its_own() {
2898 let mut run = Run::new();
2901 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2902 assert_eq!(run.go("alone\n"), "alone");
2903 }
2904
2905 #[test]
2906 fn the_predefined_files_are_named_the_way_gcc_names_them() {
2907 let mut run = Run::new();
2908 let mut opts = Predef::new();
2909 opts.defines = vec!["FOO=1".to_owned()];
2910 run.predefine("x86_64-unknown-linux-gnu", &opts);
2911 let names: Vec<&str> = run.sources.files().iter().map(|f| f.name.as_str()).collect();
2912 assert_eq!(names, ["<built-in>", "<command-line>"]);
2913 }
2914
2915 #[test]
2916 fn a_dialect_without_the_gnu_extensions_says_so() {
2917 let mut opts = Predef::new();
2918 opts.gnu_extensions = false;
2919 opts.std = Std::C99;
2920 let mut run = Run::new();
2921 run.predefine("x86_64-unknown-linux-gnu", &opts);
2922 let src = "#if defined(__STRICT_ANSI__) && __STDC_VERSION__ == 199901L && !defined(linux)\n\
2923 yes\n#endif\n";
2924 assert_eq!(run.go(src), "yes");
2925 }
2926
2927 #[test]
2928 fn the_date_and_time_are_the_same_for_the_whole_translation_unit() {
2929 let mut opts = Predef::new();
2930 opts.timestamp = Timestamp::from_unix(0);
2931 let mut run = Run::new();
2932 run.predefine("x86_64-unknown-linux-gnu", &opts);
2933 assert_eq!(run.go("__DATE__ __TIME__\n"), "\"Jan 1 1970\" \"00:00:00\"");
2934 }
2935
2936 #[test]
2937 fn a_has_operator_in_a_dead_branch_is_not_asked_about() {
2938 assert_eq!(clean("#if 0\n#if __has_include\n#endif\n#endif\nafter\n"), "after");
2940 }
2941
2942 #[test]
2943 fn a_conditional_may_not_span_an_include() {
2944 let mut run = Run::new();
2948 run.file("/dir/open.h", "#if 1\n");
2949 run.dir("/dir");
2950 run.go("#include <open.h>\nkept\n#endif\n");
2951 let messages = run.messages();
2952 assert_eq!(messages.len(), 2);
2953 assert!(messages[0].contains("unterminated"));
2954 assert!(messages[1].contains("without"));
2955 }
2956
2957 #[test]
2958 fn include_next_continues_after_the_directory_the_file_came_from() {
2959 let mut run = Run::new();
2962 run.file("/a/limits.h", "wrapper\n#include_next <limits.h>\n");
2963 run.file("/b/limits.h", "real\n");
2964 run.dir("/a");
2965 run.dir("/b");
2966 assert_eq!(run.go("#include <limits.h>\n"), "wrapper real");
2967 assert!(run.messages().is_empty());
2968 }
2969
2970 #[test]
2971 fn a_computed_include_is_expanded_first() {
2972 let mut run = Run::new();
2973 run.file("/dir/sub/thing.h", "computed\n");
2974 run.dir("/dir");
2975 let src = "#define HEADER <sub/thing.h>\n#include HEADER\n";
2976 assert_eq!(run.go(src), "computed");
2977 assert!(run.messages().is_empty());
2978 let mut run = Run::new();
2980 run.file("/dir/sub/thing.h", "computed\n");
2981 run.dir("/dir");
2982 assert_eq!(run.go("#define H \"sub/thing.h\"\n#include H\n"), "computed");
2983 }
2984
2985 #[test]
2986 fn a_header_that_is_not_there_says_where_it_looked() {
2987 let mut run = Run::new();
2988 run.dir("/dir");
2989 run.go("#include <nope.h>\n");
2990 let diagnostics = run.pp.take_diagnostics();
2991 assert_eq!(diagnostics.len(), 1);
2992 assert_eq!(diagnostics[0].code, Some("E0341"));
2993 assert_eq!(diagnostics[0].message, "`nope.h` file not found");
2994 assert!(diagnostics[0].children[0].message.contains("/dir"));
2995 }
2996
2997 #[test]
2998 fn an_include_that_is_not_a_header_name_is_reported() {
2999 let mut run = Run::new();
3000 run.go("#include 3\n");
3001 let diagnostics = run.pp.take_diagnostics();
3002 assert_eq!(diagnostics[0].code, Some("E0343"));
3003 }
3004
3005 #[test]
3006 fn a_header_that_includes_itself_stops() {
3007 let mut run = Run::new();
3008 run.file("/dir/loop.h", "#include <loop.h>\n");
3009 run.dir("/dir");
3010 run.go("#include <loop.h>\n");
3011 let diagnostics = run.pp.take_diagnostics();
3012 assert_eq!(diagnostics.len(), 1, "one complaint, not one per level");
3013 assert_eq!(diagnostics[0].code, Some("E0342"));
3014 }
3015
3016 #[test]
3017 fn an_include_in_a_dead_branch_is_not_read() {
3018 let mut run = Run::new();
3019 assert_eq!(run.go("#if 0\n#include <nothing.h>\n#endif\nafter\n"), "after");
3020 assert!(run.messages().is_empty(), "a skipped include is not resolved");
3021 }
3022
3023 #[test]
3024 fn embed_writes_the_bytes_of_the_resource() {
3025 let mut run = Run::new();
3026 run.bytes("/logo.bin", &[0, 1, 127, 128, 255]);
3027 assert_eq!(run.go("#embed \"logo.bin\"\n"), "0, 1, 127, 128, 255");
3028 assert!(run.messages().is_empty());
3029 }
3030
3031 #[test]
3032 fn an_embed_is_a_valid_initializer_on_both_sides_of_empty() {
3033 let mut run = Run::new();
3038 run.bytes("/some.bin", &[7, 8]);
3039 run.bytes("/none.bin", &[]);
3040 let line = |name: &str| {
3041 format!("{{\n#embed \"{name}\" prefix(0xEF,) suffix(,0xFE) if_empty(0)\n}}\n")
3042 };
3043 assert_eq!(run.go(&line("some.bin")), "{ 0xEF,7, 8 ,0xFE }");
3044 assert_eq!(run.go_named("/other.c", &line("none.bin")), "{ 0 }");
3045 assert!(run.messages().is_empty());
3046 }
3047
3048 #[test]
3049 fn the_limit_and_the_offset_choose_a_window_of_the_resource() {
3050 let mut run = Run::new();
3051 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
3052 assert_eq!(run.go("#embed \"eight.bin\" limit(3)\n"), "1, 2, 3");
3053 assert_eq!(
3054 run.go_named("/b.c", "#embed \"eight.bin\" gnu::offset(4) limit(3)\n"),
3055 "5, 6, 7"
3056 );
3057 assert_eq!(run.go_named("/c.c", "#embed \"eight.bin\" limit(0) if_empty(9)\n"), "9");
3060 assert_eq!(run.go_named("/d.c", "#embed \"eight.bin\" gnu::offset(99)\n"), "");
3061 assert!(run.messages().is_empty());
3062 }
3063
3064 #[test]
3065 fn the_limit_is_a_constant_expression_and_not_just_a_number() {
3066 let mut run = Run::new();
3069 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
3070 assert_eq!(
3071 run.go("#define CHUNK 2\n#embed \"eight.bin\" limit(CHUNK * 2)\n"),
3072 "1, 2, 3, 4"
3073 );
3074 assert!(run.messages().is_empty());
3075 }
3076
3077 #[test]
3078 fn a_misspelled_embed_parameter_is_refused_rather_than_ignored() {
3079 let mut run = Run::new();
3082 run.bytes("/eight.bin", &[1, 2]);
3083 assert_eq!(run.go("#embed \"eight.bin\" limits(1)\n"), "");
3084 assert_eq!(run.messages(), vec!["unknown `#embed` parameter `limits`".to_owned()]);
3085 let mut vendor = Run::new();
3086 vendor.bytes("/eight.bin", &[1, 2]);
3087 assert_eq!(vendor.go("#embed \"eight.bin\" clang::offset(1)\n"), "");
3088 assert_eq!(
3089 vendor.messages(),
3090 vec!["unknown `#embed` parameter `clang::offset`".to_owned()]
3091 );
3092 }
3093
3094 #[test]
3095 fn a_missing_embed_resource_is_reported_as_a_resource() {
3096 let mut run = Run::new();
3097 run.go("#embed <nothing.bin>\n");
3098 assert_eq!(run.messages(), vec!["`nothing.bin` resource not found".to_owned()]);
3099 }
3100
3101 #[test]
3102 fn has_embed_tells_missing_from_present_from_empty() {
3103 let mut run = Run::new();
3107 run.bytes("/some.bin", &[1]);
3108 run.bytes("/none.bin", &[]);
3109 let src = "#if __has_embed(\"none.bin\") == __STDC_EMBED_EMPTY__\nempty\n#endif\n\
3110 #if __has_embed(\"some.bin\") == __STDC_EMBED_FOUND__\nfound\n#endif\n\
3111 #if __has_embed(\"gone.bin\") == __STDC_EMBED_NOT_FOUND__\ngone\n#endif\n";
3112 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
3113 assert_eq!(run.go(src), "empty found gone");
3114 assert!(run.messages().is_empty());
3115 }
3116
3117 #[test]
3118 fn has_embed_takes_the_limit_into_account() {
3119 let mut run = Run::new();
3122 run.bytes("/some.bin", &[1, 2, 3]);
3123 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
3124 let src = "#if __has_embed(\"some.bin\" limit(0)) == __STDC_EMBED_EMPTY__\nempty\n#endif\n";
3125 assert_eq!(run.go(src), "empty");
3126 assert!(run.messages().is_empty());
3127 }
3128
3129 #[test]
3130 fn a_directive_may_have_space_before_the_hash_and_after_it() {
3131 assert_eq!(clean(" # define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
3132 }
3133
3134 #[test]
3135 fn a_definition_survives_across_a_conditional() {
3136 assert_eq!(clean("#if 1\n#define F 7\n#endif\nF\n"), "7");
3137 }
3138
3139 #[test]
3140 fn an_empty_if_expression_is_reported() {
3141 let mut run = Run::new();
3142 run.go("#if\n#endif\n");
3143 assert_eq!(run.messages(), vec!["`#if` with no expression".to_owned()]);
3144 }
3145
3146 #[test]
3147 fn the_file_and_the_line_say_where_the_use_is() {
3148 let mut run = Run::new();
3149 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3150 assert_eq!(run.go("__FILE__ __LINE__\n__LINE__\n"), "\"/main.c\" 1 2");
3151 assert!(run.messages().is_empty());
3152 }
3153
3154 #[test]
3155 fn a_macro_that_mentions_the_line_answers_with_the_call() {
3156 let mut run = Run::new();
3157 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3158 run.file("/where.h", "#define WHERE __FILE__ __LINE__\n");
3159 assert_eq!(run.go("#include \"where.h\"\n\n\nWHERE\n"), "\"/main.c\" 4");
3163 assert!(run.messages().is_empty());
3164 }
3165
3166 #[test]
3167 fn the_file_name_is_the_file_without_the_directories() {
3168 let mut run = Run::new();
3169 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3170 assert_eq!(run.go_named("/deep/down/main.c", "__FILE_NAME__\n"), "\"main.c\"");
3171 }
3172
3173 #[test]
3174 fn a_backslash_in_the_name_is_escaped() {
3175 let mut run = Run::new();
3176 run.predefine("x86_64-pc-windows-msvc", &Predef::new());
3177 let text = run.go_named("C:\\src\\main.c", "__FILE__ __FILE_NAME__\n");
3180 assert_eq!(text, "\"C:\\\\src\\\\main.c\" \"main.c\"");
3181 }
3182
3183 #[test]
3184 fn the_base_file_is_the_one_named_on_the_command_line() {
3185 let mut run = Run::new();
3186 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3187 run.file("/deep.h", "__FILE__ __BASE_FILE__\n");
3188 assert_eq!(run.go("#include \"deep.h\"\n"), "\"/deep.h\" \"/main.c\"");
3189 assert!(run.messages().is_empty());
3190 }
3191
3192 #[test]
3193 fn the_include_level_counts_the_headers_above_it() {
3194 let mut run = Run::new();
3195 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3196 run.file("/one.h", "__INCLUDE_LEVEL__\n#include \"two.h\"\n");
3197 run.file("/two.h", "__INCLUDE_LEVEL__\n");
3198 assert_eq!(run.go("__INCLUDE_LEVEL__\n#include \"one.h\"\n"), "0 1 2");
3199 assert!(run.messages().is_empty());
3200 }
3201
3202 #[test]
3203 fn the_counter_is_a_different_number_every_time() {
3204 let mut run = Run::new();
3205 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3206 assert_eq!(run.go("__COUNTER__ __COUNTER__ __COUNTER__\n"), "0 1 2");
3207 }
3208
3209 #[test]
3210 fn the_counter_advances_once_per_argument_rather_than_once_per_use() {
3211 let mut run = Run::new();
3212 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3213 assert_eq!(run.go("#define TWICE(x) x x\nTWICE(__COUNTER__) __COUNTER__\n"), "0 0 1");
3217 }
3218
3219 #[test]
3220 fn the_line_is_a_number_an_if_can_use() {
3221 let mut run = Run::new();
3222 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3223 assert_eq!(run.go("#if __LINE__ == 1 && __INCLUDE_LEVEL__ == 0\nyes\n#endif\n"), "yes");
3224 assert!(run.messages().is_empty());
3225 }
3226
3227 #[test]
3228 fn the_dynamic_macros_are_defined_like_any_others() {
3229 let mut run = Run::new();
3230 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3231 let src = "#ifdef __FILE__\nyes\n#endif\n#undef __LINE__\n#ifndef __LINE__\ngone\n#endif\n";
3232 assert_eq!(run.go(src), "yes gone");
3233 assert!(run.messages().is_empty(), "`#undef` of a builtin is allowed, as it is in GCC");
3234 }
3235
3236 #[test]
3237 fn redefining_a_dynamic_macro_warns_and_points_at_the_built_in_file() {
3238 let mut run = Run::new();
3239 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
3240 assert_eq!(run.go("#define __FILE__ \"mine.c\"\n__FILE__\n"), "\"mine.c\"");
3241 let complaints = run.pp.take_diagnostics();
3242 assert_eq!(complaints.len(), 1);
3243 assert_eq!(complaints[0].code, Some("W0301"));
3244 let previous = complaints[0].children.first().expect("a note saying where it was");
3245 assert_eq!(run.sources.lookup(previous.span.lo).map(|loc| loc.file), {
3246 let built_in = run.sources.files().iter().find(|f| f.name == BUILT_IN);
3247 built_in.map(|f| f.id)
3248 });
3249 }
3250
3251 #[test]
3252 fn destringizing_undoes_what_stringizing_did() {
3253 assert_eq!(destringize(r#""a \"b\" c""#), r#"a "b" c"#);
3254 assert_eq!(destringize(r#""a \\ b""#), r"a \ b");
3255 assert_eq!(destringize(r#"L"wide""#), "wide");
3256 }
3257}