1use std::collections::HashMap;
18use std::path::{Path, PathBuf};
19
20use rucc_base::{Interner, Symbol};
21use rucc_diag::{Diagnostic, FileId, SourceMapFull, Span};
22use rucc_gnu::Kind;
23use rucc_lex::{Options, PpToken, PpTokenKind, Punct, TokenFlags, tokenize};
24use rucc_session::{Found, IncludeForm};
25use rucc_target::TargetInfo;
26
27use crate::cond;
28use crate::embed;
29use crate::expand::Expander;
30use crate::include::{
31 Context, Frame, Header, Reader, directory_of, header_from_token, header_from_tokens, spelling,
32};
33use crate::macros::{Builtin, MacroTable, parse_define};
34use crate::predef::{BUILT_IN, COMMAND_LINE, Predef, built_in, command_line};
35use crate::token::Tok;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39enum Guard {
40 Once,
42 Macro(Symbol),
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51enum Scan {
52 Start,
54 Inside(Symbol),
56 Closed(Symbol),
58 No,
60}
61
62#[derive(Debug)]
64struct Cond {
65 span: Span,
67 live: bool,
70 taken: bool,
73 enclosing_live: bool,
75 seen_else: bool,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct LineDirective {
82 pub span: Span,
84 pub line: u32,
86 pub file: Option<Symbol>,
88}
89
90#[derive(Debug, Default)]
95pub struct Preprocessor {
96 macros: MacroTable,
97 expander: Expander,
98 diagnostics: Vec<Diagnostic>,
99 conds: Vec<Cond>,
100 lines: Vec<LineDirective>,
101 stack: Vec<Frame>,
103 seen: HashMap<PathBuf, Guard>,
105}
106
107impl Preprocessor {
108 pub fn new() -> Preprocessor {
110 Preprocessor::default()
111 }
112
113 pub fn macros(&self) -> &MacroTable {
115 &self.macros
116 }
117
118 pub fn macros_mut(&mut self) -> &mut MacroTable {
120 &mut self.macros
121 }
122
123 pub fn diagnostics(&self) -> &[Diagnostic] {
125 &self.diagnostics
126 }
127
128 pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
130 std::mem::take(&mut self.diagnostics)
131 }
132
133 pub fn line_directives(&self) -> &[LineDirective] {
141 &self.lines
142 }
143
144 pub fn predefine(
156 &mut self,
157 target: &TargetInfo,
158 opts: &Predef,
159 cx: &mut Context<'_>,
160 ) -> Result<(), SourceMapFull> {
161 let names = Names::new(cx.interner);
162 let file = self.synthetic(BUILT_IN, built_in(target, opts), cx, &names)?;
163 let start = cx.sources.file(file).start;
169 for (spelling, builtin) in Builtin::ALL {
170 let name = cx.interner.intern(spelling);
171 self.macros.define_builtin(name, builtin, Span::new(start, start));
172 }
173 let text = command_line(opts);
174 if !text.is_empty() {
175 self.synthetic(COMMAND_LINE, text, cx, &names)?;
176 }
177 Ok(())
178 }
179
180 fn synthetic(
182 &mut self,
183 name: &str,
184 text: String,
185 cx: &mut Context<'_>,
186 names: &Names,
187 ) -> Result<FileId, SourceMapFull> {
188 let file = cx.sources.add(name, text.into_bytes())?;
189 let mut out = Vec::new();
190 self.stack.push(Frame { at: Span::DUMMY, path: PathBuf::from(name), dir: None, next: 0 });
194 self.process(file, &mut out, cx, names);
195 self.stack.clear();
196 debug_assert!(out.is_empty(), "{name} is directives only and produces no tokens");
197 Ok(file)
198 }
199
200 pub fn run(&mut self, file: FileId, cx: &mut Context<'_>) -> Vec<Tok> {
205 let names = Names::new(cx.interner);
206 let mut out = Vec::new();
207 let name = cx.sources.file(file).name.clone();
208 let dir = directory_of(&name);
209 self.stack.push(Frame { at: Span::DUMMY, path: PathBuf::from(name), dir, next: 0 });
212 self.process(file, &mut out, cx, &names);
213 self.stack.clear();
214 out
215 }
216
217 fn process(&mut self, file: FileId, out: &mut Vec<Tok>, cx: &mut Context<'_>, names: &Names) {
219 let bytes = cx.sources.file(file).shared_bytes();
222 let start = cx.sources.file(file).start;
223 let mut reader = Reader::new(bytes.as_slice(), start, cx.lex);
224 let depth_on_entry = self.conds.len();
225 let mut text: Vec<Tok> = Vec::new();
229 let mut body: Vec<PpToken> = Vec::new();
230 let mut scan = Scan::Start;
231
232 loop {
233 let was_live = self.live();
234 let first = reader.next(cx.interner);
235 if first.is_eof() {
236 break;
237 }
238 if is_directive(first) {
239 self.flush(&mut text, out, cx, names);
240 body.clear();
241 let name_tok = reader.next(cx.interner);
242 if name_tok.is_eof() || name_tok.flags.has(TokenFlags::START_OF_LINE) {
245 reader.put_back(name_tok);
246 continue;
247 }
248 body.push(name_tok);
249 if was_live && is_include(ident_of(&name_tok), names) {
254 if let Some(header) = reader.header_name(cx.interner) {
255 body.push(header);
256 }
257 }
258 reader.line(cx.interner, &mut body);
259 let opens =
260 matches!(scan, Scan::Start).then(|| guard_opener(&body, names)).flatten();
261 self.directive(&body, first.span, out, cx, names);
262 scan = match scan {
263 Scan::Start => match opens {
267 Some(name) if self.conds.len() == depth_on_entry + 1 => Scan::Inside(name),
268 _ => Scan::No,
269 },
270 Scan::Inside(name) if self.conds.len() == depth_on_entry => Scan::Closed(name),
271 Scan::Inside(name) => Scan::Inside(name),
272 Scan::Closed(_) | Scan::No => Scan::No,
273 };
274 } else {
275 body.clear();
276 reader.line(cx.interner, &mut body);
277 if self.live() {
278 text.push(Tok::new(first));
279 text.extend(body.iter().copied().map(Tok::new));
280 }
281 if !matches!(scan, Scan::Inside(_)) {
283 scan = Scan::No;
284 }
285 }
286 let complaints = reader.take_diagnostics();
289 if was_live || self.live() {
290 self.diagnostics.extend(complaints);
291 }
292 }
293 self.flush(&mut text, out, cx, names);
294 self.diagnostics.extend(reader.take_diagnostics());
295
296 if let Scan::Closed(name) = scan {
299 if self.macros.is_defined(name) {
300 if let Some(frame) = self.stack.last() {
301 self.seen.entry(frame.path.clone()).or_insert(Guard::Macro(name));
302 }
303 }
304 }
305
306 for cond in self.conds.drain(depth_on_entry..) {
309 self.diagnostics
310 .push(Diagnostic::error("unterminated `#if`", cond.span).with_code("E0330"));
311 }
312 }
313
314 fn live(&self) -> bool {
316 self.conds.last().is_none_or(|c| c.live)
317 }
318
319 fn flush(
321 &mut self,
322 text: &mut Vec<Tok>,
323 out: &mut Vec<Tok>,
324 cx: &mut Context<'_>,
325 names: &Names,
326 ) {
327 if text.is_empty() {
328 return;
329 }
330 let taken = std::mem::take(text);
331 let expanded = self.expander.expand_toks(taken, &self.macros, cx.interner, cx.sources);
332 self.diagnostics.append(&mut self.expander.take_diagnostics());
333 let expanded = self.resolve_has(expanded, cx, names, Pass::Text);
338 self.pragma_operator(expanded, out, cx.interner, names);
339 }
340
341 fn directive(
343 &mut self,
344 body: &[PpToken],
345 hash: Span,
346 out: &mut Vec<Tok>,
347 cx: &mut Context<'_>,
348 names: &Names,
349 ) {
350 let Some(first) = body.first().copied() else {
351 return;
352 };
353 let name = ident_of(&first);
354 let rest = &body[1..];
355
356 if name == Some(names.r#if) {
359 let value = self.live() && self.eval(rest, hash, cx, names);
360 self.open(hash, value);
361 return;
362 }
363 if name == Some(names.ifdef) || name == Some(names.ifndef) {
364 let want = name == Some(names.ifdef);
365 let value = self.live() && self.defined_check(rest, hash, want, names);
366 self.open(hash, value);
367 return;
368 }
369 if name == Some(names.elif) || name == Some(names.elifdef) || name == Some(names.elifndef) {
370 self.elif(name, rest, hash, cx, names);
371 return;
372 }
373 if name == Some(names.r#else) {
374 self.branch_else(rest, hash);
375 return;
376 }
377 if name == Some(names.endif) {
378 self.endif(rest, hash);
379 return;
380 }
381 if !self.live() {
382 return;
386 }
387
388 let interner = &mut *cx.interner;
389 if name == Some(names.define) {
390 let (def, diagnostics) = parse_define(rest, interner);
391 self.diagnostics.extend(diagnostics);
392 if let Some(def) = def {
393 if let Some(problem) = self.macros.define(def, interner) {
394 self.diagnostics.push(problem);
395 }
396 }
397 } else if name == Some(names.undef) {
398 self.undef(rest, hash, interner);
399 } else if name == Some(names.error) || name == Some(names.warning) {
400 self.message(rest, hash, name == Some(names.error), interner);
401 } else if name == Some(names.line) {
402 self.line(rest, hash, cx);
403 } else if name == Some(names.pragma) {
404 if rest.len() == 1 && ident_of(&rest[0]) == Some(names.once) {
411 self.pragma_once(hash);
412 } else {
413 self.pass_through(body, hash, out);
414 }
415 } else if name == Some(names.include) || name == Some(names.include_next) {
416 self.include(rest, hash, name == Some(names.include_next), out, cx, names);
417 } else if name == Some(names.embed) {
418 self.embed(rest, hash, out, cx);
419 } else {
420 self.diagnostics.push(
421 Diagnostic::error("invalid preprocessing directive", first.span).with_code("E0332"),
422 );
423 }
424 }
425
426 fn pragma_once(&mut self, hash: Span) {
428 if self.stack.len() <= 1 {
431 self.diagnostics.push(
432 Diagnostic::warning("`#pragma once` in the main file", hash).with_code("W0332"),
433 );
434 return;
435 }
436 if let Some(frame) = self.stack.last() {
437 self.seen.insert(frame.path.clone(), Guard::Once);
438 }
439 }
440
441 fn skip(&self, path: &Path) -> bool {
443 match self.seen.get(path) {
444 Some(Guard::Once) => true,
445 Some(Guard::Macro(name)) => self.macros.is_defined(*name),
446 None => false,
447 }
448 }
449
450 fn pass_through(&mut self, body: &[PpToken], hash: Span, out: &mut Vec<Tok>) {
452 let _ = self;
453 out.push(Tok::synthetic(
454 PpTokenKind::Punct(Punct::Hash),
455 None,
456 TokenFlags::START_OF_LINE,
457 hash,
458 ));
459 for (at, token) in body.iter().copied().enumerate() {
464 let mut token = Tok::new(token);
465 if at == 0 {
466 token.flags = token.flags.without(TokenFlags::LEADING_SPACE);
467 }
468 out.push(token);
469 }
470 }
471
472 fn include(
474 &mut self,
475 rest: &[PpToken],
476 hash: Span,
477 is_next: bool,
478 out: &mut Vec<Tok>,
479 cx: &mut Context<'_>,
480 names: &Names,
481 ) {
482 let Some(header) = self.header_of(rest, hash, cx) else {
483 return;
484 };
485 let (form, relative_to, from) = self.where_to_look(&header, is_next, cx);
486 let found = cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from);
487 let Some(found) = found else {
488 let tried = cx.search.tried(&header.name, form, relative_to.as_deref(), from);
489 let where_looked = if tried.is_empty() && Path::new(&header.name).is_absolute() {
493 "the name is an absolute path, so the search path was not used".to_owned()
494 } else if tried.is_empty() {
495 "the include search path is empty".to_owned()
496 } else {
497 let list: Vec<String> =
498 tried.iter().map(|d| d.to_string_lossy().into_owned()).collect();
499 format!("searched: {}", list.join(", "))
500 };
501 self.diagnostics.push(
502 Diagnostic::error(format!("`{}` file not found", header.name), hash)
503 .with_code("E0341")
504 .note(where_looked, hash),
505 );
506 return;
507 };
508 if self.skip(&found.path) {
513 return;
514 }
515 if self.stack.len() >= cx.max_include_depth as usize {
516 let mut diagnostic =
517 Diagnostic::error("`#include` nested too deeply", hash).with_code("E0342").note(
518 "a header that includes itself with no include guard is the usual cause",
519 hash,
520 );
521 if let Some(outer) = self.stack.first().filter(|f| !f.at.is_dummy()) {
522 diagnostic = diagnostic.note("the outermost include is here", outer.at);
523 }
524 self.diagnostics.push(diagnostic);
525 return;
526 }
527 let added = cx.sources.add_shared(found.name.clone(), found.bytes.clone(), Some(hash));
528 let file = match added {
529 Ok(file) => file,
530 Err(full) => {
531 self.diagnostics.push(Diagnostic::error(full.to_string(), hash).with_code("E0344"));
532 return;
533 }
534 };
535 self.stack.push(Frame {
536 at: hash,
537 dir: found.path.parent().map(Path::to_path_buf),
538 path: found.path,
539 next: found.next,
540 });
541 self.process(file, out, cx, names);
542 self.stack.pop();
543 }
544
545 fn embed(&mut self, rest: &[PpToken], hash: Span, out: &mut Vec<Tok>, cx: &mut Context<'_>) {
547 let Some((header, params)) = self.embed_line(rest, hash, cx) else {
548 return;
549 };
550 let Some(found) = self.find(&header, false, cx) else {
551 self.diagnostics.push(
552 Diagnostic::error(format!("`{}` resource not found", header.name), hash)
553 .with_code("E0341")
554 .note("an `#embed` resource is looked for on the include path", hash),
555 );
556 return;
557 };
558 embed::tokens(found.bytes.as_slice(), ¶ms, hash, cx.interner, out);
563 }
564
565 fn embed_line(
567 &mut self,
568 rest: &[PpToken],
569 hash: Span,
570 cx: &mut Context<'_>,
571 ) -> Option<(Header, embed::Params)> {
572 if rest.is_empty() {
573 self.bad_header(hash);
574 return None;
575 }
576 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
577 let line = if line[0].kind == PpTokenKind::HeaderName {
583 line
584 } else {
585 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
586 self.diagnostics.append(&mut self.expander.take_diagnostics());
587 expanded
588 };
589 let Some(used) = embed::header_length(&line) else {
590 self.bad_header(line.first().map_or(hash, |t| t.report_span()));
591 return None;
592 };
593 let header = if line[0].kind == PpTokenKind::HeaderName {
594 header_from_token(spelling(line[0], cx.interner))
595 } else {
596 let spellings: Vec<&str> =
597 line[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
598 header_from_tokens(&spellings)
599 };
600 let Some(header) = header else {
601 self.bad_header(line[0].report_span());
602 return None;
603 };
604 let params = self.embed_params(&line[used..], hash, cx)?;
605 Some((header, params))
606 }
607
608 fn embed_params(
610 &mut self,
611 line: &[Tok],
612 at: Span,
613 cx: &mut Context<'_>,
614 ) -> Option<embed::Params> {
615 let Preprocessor { expander, macros, diagnostics, .. } = self;
616 let sources = &mut *cx.sources;
617 let mut expand = |toks: Vec<Tok>, interner: &mut Interner| {
618 expander.expand_toks(toks, macros, interner, sources)
619 };
620 let params = embed::parse(line, at, cx.interner, diagnostics, &mut expand);
621 self.diagnostics.append(&mut self.expander.take_diagnostics());
622 params
623 }
624
625 fn where_to_look(
636 &self,
637 header: &Header,
638 is_next: bool,
639 cx: &Context<'_>,
640 ) -> (IncludeForm, Option<PathBuf>, usize) {
641 let form = if header.angled { IncludeForm::Angled } else { IncludeForm::Quoted };
642 let frame = self.stack.last();
643 let from = if is_next {
644 frame.map_or(0, |f| f.next).max(cx.search.start(form))
645 } else {
646 cx.search.start(form)
647 };
648 let relative_to = if is_next { None } else { frame.and_then(|f| f.dir.clone()) };
649 (form, relative_to, from)
650 }
651
652 fn find(&self, header: &Header, is_next: bool, cx: &Context<'_>) -> Option<Found> {
654 let (form, relative_to, from) = self.where_to_look(header, is_next, cx);
655 cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from)
656 }
657
658 fn header_of(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>) -> Option<Header> {
660 if let Some(first) = rest.first().copied() {
661 if first.kind == PpTokenKind::HeaderName {
662 let text = first.value.map_or("", |v| cx.interner.resolve(v));
663 let header = header_from_token(text);
664 if header.is_none() {
665 self.bad_header(first.span);
666 }
667 self.extra_tokens(&rest[1..], "#include");
668 return header;
669 }
670 }
671 if rest.is_empty() {
675 self.bad_header(hash);
676 return None;
677 }
678 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
679 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
680 self.diagnostics.append(&mut self.expander.take_diagnostics());
681 let spellings: Vec<&str> = expanded.iter().map(|t| spelling(*t, cx.interner)).collect();
682 let header = header_from_tokens(&spellings);
683 if header.is_none() {
684 let at = expanded.first().map_or(hash, |t| t.report_span());
685 self.bad_header(at);
686 }
687 header
688 }
689
690 fn bad_operand(&mut self, tok: Tok, at: Span, interner: &Interner) {
692 self.diagnostics.push(
693 Diagnostic::error(
694 format!("expected an identifier as the operand of `{}`", spelling(tok, interner)),
695 at,
696 )
697 .with_code("E0345"),
698 );
699 }
700
701 fn bad_header(&mut self, at: Span) {
702 self.diagnostics.push(
703 Diagnostic::error("expected a file name in `<>` or `\"\"`", at).with_code("E0343"),
704 );
705 }
706
707 fn open(&mut self, span: Span, value: bool) {
709 let enclosing_live = self.live();
710 self.conds.push(Cond {
711 span,
712 live: enclosing_live && value,
713 taken: value,
714 enclosing_live,
715 seen_else: false,
716 });
717 }
718
719 fn elif(
720 &mut self,
721 name: Option<Symbol>,
722 rest: &[PpToken],
723 hash: Span,
724 cx: &mut Context<'_>,
725 names: &Names,
726 ) {
727 let Some(top) = self.conds.last() else {
728 self.stray("elif", hash);
729 return;
730 };
731 if top.seen_else {
732 self.diagnostics
733 .push(Diagnostic::error("`#elif` after `#else`", hash).with_code("E0333"));
734 return;
735 }
736 let (enclosing_live, already_taken) = (top.enclosing_live, top.taken);
739 let consider = enclosing_live && !already_taken;
740 let value = if !consider {
741 false
742 } else if name == Some(names.elif) {
743 self.eval(rest, hash, cx, names)
744 } else {
745 self.defined_check(rest, hash, name == Some(names.elifdef), names)
746 };
747 let top = self.conds.last_mut().expect("checked above and nothing popped");
748 top.live = consider && value;
749 top.taken = already_taken || value;
750 }
751
752 fn branch_else(&mut self, rest: &[PpToken], hash: Span) {
753 let Some(top) = self.conds.last_mut() else {
754 self.stray("else", hash);
755 return;
756 };
757 if top.seen_else {
758 self.diagnostics.push(Diagnostic::error("a second `#else`", hash).with_code("E0333"));
759 return;
760 }
761 top.live = top.enclosing_live && !top.taken;
762 top.taken = true;
763 top.seen_else = true;
764 let enclosing_live = top.enclosing_live;
765 if enclosing_live {
766 self.extra_tokens(rest, "#else");
767 }
768 }
769
770 fn endif(&mut self, rest: &[PpToken], hash: Span) {
771 if self.conds.pop().is_none() {
772 self.stray("endif", hash);
773 return;
774 }
775 if self.live() {
776 self.extra_tokens(rest, "#endif");
777 }
778 }
779
780 fn stray(&mut self, what: &str, hash: Span) {
781 self.diagnostics
782 .push(Diagnostic::error(format!("`#{what}` without `#if`"), hash).with_code("E0334"));
783 }
784
785 fn extra_tokens(&mut self, rest: &[PpToken], what: &str) {
790 if let Some(first) = rest.first() {
791 self.diagnostics.push(
792 Diagnostic::warning(format!("extra tokens after `{what}`"), first.span)
793 .with_code("W0330"),
794 );
795 }
796 }
797
798 fn eval(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>, names: &Names) -> bool {
800 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
801 let line = self.resolve_defined(line, cx.interner, names);
807 let line = self.resolve_has(line, cx, names, Pass::Headers);
812 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
813 self.diagnostics.append(&mut self.expander.take_diagnostics());
814 let line = self.resolve_defined(line, cx.interner, names);
815 let line = self.resolve_has(line, cx, names, Pass::Rest);
816 cond::evaluate(&line, cx.interner, &mut self.diagnostics, hash)
817 }
818
819 fn resolve_has(
824 &mut self,
825 line: Vec<Tok>,
826 cx: &mut Context<'_>,
827 names: &Names,
828 pass: Pass,
829 ) -> Vec<Tok> {
830 if !line.iter().any(|t| t.ident().is_some_and(|n| names.has.op(n).is_some())) {
831 return line;
832 }
833 let mut out = Vec::with_capacity(line.len());
834 let mut at = 0;
835 while at < line.len() {
836 let tok = line[at];
837 let op = tok.ident().and_then(|n| names.has.op(n));
838 let Some(op) = op.filter(|op| pass.answers(*op)) else {
839 if pass == Pass::Text && op.is_some_and(Op::is_header) {
840 self.outside_a_directive(tok, cx);
841 }
842 out.push(tok);
843 at += 1;
844 continue;
845 };
846 let Some((operand, after)) = arguments(&line, at + 1) else {
847 if pass != Pass::Headers {
851 self.diagnostics.push(
852 Diagnostic::error(
853 format!("expected `(` after `{}`", spelling(tok, cx.interner)),
854 tok.report_span(),
855 )
856 .with_code("E0345"),
857 );
858 }
859 out.push(tok);
860 at += 1;
861 continue;
862 };
863 at = after;
864 let value = self.ask(op, operand, tok, cx);
867 let sym = cx.interner.intern(&value.to_string());
868 out.push(Tok::synthetic(PpTokenKind::Number, Some(sym), tok.flags, tok.report_span()));
869 }
870 out
871 }
872
873 fn outside_a_directive(&mut self, tok: Tok, cx: &Context<'_>) {
881 self.diagnostics.push(
882 Diagnostic::error(
883 format!(
884 "`{}` used outside of a preprocessing directive",
885 spelling(tok, cx.interner)
886 ),
887 tok.report_span(),
888 )
889 .with_code("E0350"),
890 );
891 }
892
893 fn ask(&mut self, op: Op, operand: &[Tok], tok: Tok, cx: &mut Context<'_>) -> u32 {
895 let at = operand.first().map_or(tok.report_span(), |t| t.report_span());
896 match op {
897 Op::Include | Op::IncludeNext => {
898 let spellings: Vec<&str> =
899 operand.iter().map(|t| spelling(*t, cx.interner)).collect();
900 let Some(header) = header_from_tokens(&spellings) else {
901 self.bad_header(at);
902 return 0;
903 };
904 u32::from(self.find(&header, op == Op::IncludeNext, cx).is_some())
905 }
906 Op::Embed => {
907 let Some(used) = embed::header_length(operand) else {
912 self.bad_header(at);
913 return 0;
914 };
915 let header = if operand[0].kind == PpTokenKind::HeaderName {
916 header_from_token(spelling(operand[0], cx.interner))
917 } else {
918 let spellings: Vec<&str> =
919 operand[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
920 header_from_tokens(&spellings)
921 };
922 let Some(header) = header else {
923 self.bad_header(at);
924 return 0;
925 };
926 let Some(params) = self.embed_params(&operand[used..], at, cx) else {
931 return 0;
932 };
933 match self.find(&header, false, cx) {
934 None => 0,
935 Some(found) => {
936 let taken = params.taken(found.bytes.as_slice().len() as u64);
937 if taken == 0 { 2 } else { 1 }
938 }
939 }
940 }
941 Op::BuildingModule => {
942 if attribute_name(operand, cx.interner).is_none() {
943 self.bad_operand(tok, at, cx.interner);
944 }
945 0
952 }
953 Op::Table(kind) => {
954 let Some(name) = attribute_name(operand, cx.interner) else {
955 self.bad_operand(tok, at, cx.interner);
956 return 0;
957 };
958 match kind {
959 Kind::Attribute => rucc_gnu::has_attribute(name),
960 Kind::CAttribute => rucc_gnu::has_c_attribute(name),
961 Kind::Builtin => rucc_gnu::has_builtin(name),
962 Kind::Feature => rucc_gnu::has_feature(name),
963 Kind::Extension => rucc_gnu::has_extension(name),
964 }
965 }
966 }
967 }
968
969 fn resolve_defined(
971 &mut self,
972 line: Vec<Tok>,
973 interner: &mut Interner,
974 names: &Names,
975 ) -> Vec<Tok> {
976 if !line.iter().any(|t| t.ident() == Some(names.defined)) {
977 return line;
978 }
979 let mut out = Vec::with_capacity(line.len());
980 let mut at = 0;
981 while at < line.len() {
982 let tok = line[at];
983 if tok.ident() != Some(names.defined) {
984 out.push(tok);
985 at += 1;
986 continue;
987 }
988 let parenthesised = line.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
989 let name_at = if parenthesised { at + 2 } else { at + 1 };
990 let name = line.get(name_at).and_then(|t| t.ident());
991 let Some(name) = name else {
992 self.diagnostics.push(
993 Diagnostic::error("`defined` without a macro name", tok.report_span())
994 .with_code("E0335"),
995 );
996 out.push(tok);
997 at += 1;
998 continue;
999 };
1000 at = name_at + 1;
1001 if parenthesised {
1002 if line.get(at).is_some_and(|t| t.is(Punct::RParen)) {
1003 at += 1;
1004 } else {
1005 self.diagnostics.push(
1006 Diagnostic::error("expected `)` after `defined`", tok.report_span())
1007 .with_code("E0335"),
1008 );
1009 }
1010 }
1011 let value = self.macros.is_defined(name) || names.has.op(name).is_some();
1015 out.push(number(value, tok.flags, tok.report_span(), interner));
1016 }
1017 out
1018 }
1019
1020 fn defined_check(
1022 &mut self,
1023 rest: &[PpToken],
1024 hash: Span,
1025 want_defined: bool,
1026 names: &Names,
1027 ) -> bool {
1028 let Some(name) = rest.first().and_then(ident_of) else {
1029 self.diagnostics.push(
1030 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1031 .with_code("E0336"),
1032 );
1033 return false;
1034 };
1035 self.extra_tokens(&rest[1..], if want_defined { "#ifdef" } else { "#ifndef" });
1036 let defined = self.macros.is_defined(name) || names.has.op(name).is_some();
1037 defined == want_defined
1038 }
1039
1040 fn undef(&mut self, rest: &[PpToken], hash: Span, interner: &Interner) {
1041 let Some(name) = rest.first().and_then(ident_of) else {
1042 self.diagnostics.push(
1043 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1044 .with_code("E0336"),
1045 );
1046 return;
1047 };
1048 let text = interner.resolve(name);
1051 if text == "defined" || text.starts_with("__STDC_") {
1052 self.diagnostics.push(
1053 Diagnostic::error(format!("`{text}` cannot be undefined"), rest[0].span)
1054 .with_code("E0337"),
1055 );
1056 return;
1057 }
1058 self.macros.undef(name);
1059 self.extra_tokens(&rest[1..], "#undef");
1060 }
1061
1062 fn message(&mut self, rest: &[PpToken], hash: Span, fatal: bool, interner: &Interner) {
1064 let text = spell_line(rest, interner);
1065 let span = rest.first().map_or(hash, |t| t.span.to(last_span(rest)));
1066 let diag = if fatal {
1067 Diagnostic::error(text, span).with_code("E0338")
1068 } else {
1069 Diagnostic::warning(text, span).with_code("W0331")
1070 };
1071 self.diagnostics.push(diag);
1072 }
1073
1074 fn line(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>) {
1079 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
1080 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
1081 self.diagnostics.append(&mut self.expander.take_diagnostics());
1082 let interner = &mut *cx.interner;
1083
1084 let number_text = line
1085 .first()
1086 .filter(|t| t.kind == PpTokenKind::Number)
1087 .and_then(|t| t.value)
1088 .map(|v| interner.resolve(v));
1089 let Some(parsed) = number_text.and_then(|t| t.parse::<u64>().ok()) else {
1090 self.diagnostics.push(
1091 Diagnostic::error(
1092 "`#line` needs a decimal line number",
1093 line.first().map_or(hash, |t| t.report_span()),
1094 )
1095 .with_code("E0339"),
1096 );
1097 return;
1098 };
1099 if parsed == 0 || parsed > 2_147_483_647 {
1102 self.diagnostics.push(
1103 Diagnostic::error("`#line` number is out of range", line[0].report_span())
1104 .with_code("E0339"),
1105 );
1106 return;
1107 }
1108
1109 let mut file = None;
1110 if let Some(second) = line.get(1) {
1111 if second.kind == PpTokenKind::StringLit {
1112 file = second.value;
1113 } else {
1114 self.diagnostics.push(
1115 Diagnostic::error(
1116 "`#line` file name must be a string literal",
1117 second.report_span(),
1118 )
1119 .with_code("E0339"),
1120 );
1121 return;
1122 }
1123 }
1124 #[expect(
1125 clippy::cast_possible_truncation,
1126 reason = "the range check above keeps this inside i32, let alone u32"
1127 )]
1128 self.lines.push(LineDirective { span: hash, line: parsed as u32, file });
1129 }
1130
1131 fn pragma_operator(
1137 &mut self,
1138 expanded: Vec<Tok>,
1139 out: &mut Vec<Tok>,
1140 interner: &mut Interner,
1141 names: &Names,
1142 ) {
1143 if !expanded.iter().any(|t| t.ident() == Some(names.pragma_op)) {
1144 out.extend(expanded);
1145 return;
1146 }
1147 let mut at = 0;
1148 let mut ends_a_line = false;
1153 while at < expanded.len() {
1154 let mut tok = expanded[at];
1155 if tok.ident() != Some(names.pragma_op) {
1156 if ends_a_line {
1157 tok.flags = tok.flags.with(TokenFlags::START_OF_LINE);
1158 ends_a_line = false;
1159 }
1160 out.push(tok);
1161 at += 1;
1162 continue;
1163 }
1164 let open = expanded.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1165 let text = expanded.get(at + 2).filter(|t| t.kind == PpTokenKind::StringLit);
1166 let close = expanded.get(at + 3).is_some_and(|t| t.is(Punct::RParen));
1167 let (Some(text), true, true) = (text, open, close) else {
1168 self.diagnostics.push(
1169 Diagnostic::error("`_Pragma` takes a single string literal", tok.report_span())
1170 .with_code("E0340"),
1171 );
1172 out.push(tok);
1173 at += 1;
1174 continue;
1175 };
1176 let literal = text.value.map(|v| interner.resolve(v)).unwrap_or_default();
1177 let body = destringize(literal);
1178 self.emit_pragma(&body, tok, out, interner, names);
1179 ends_a_line = true;
1180 at += 4;
1181 }
1182 }
1183
1184 fn emit_pragma(
1186 &mut self,
1187 body: &str,
1188 at: Tok,
1189 out: &mut Vec<Tok>,
1190 interner: &mut Interner,
1191 names: &Names,
1192 ) {
1193 let span = at.report_span();
1194 let (tokens, diagnostics) = tokenize(body.as_bytes(), 0, Options::new(), interner);
1195 self.diagnostics.extend(
1198 diagnostics
1199 .into_iter()
1200 .map(|d| Diagnostic::new(d.severity, d.message, span).with_code("E0340")),
1201 );
1202 out.push(Tok::synthetic(
1203 PpTokenKind::Punct(Punct::Hash),
1204 None,
1205 TokenFlags::START_OF_LINE,
1206 span,
1207 ));
1208 out.push(Tok::synthetic(PpTokenKind::Ident, Some(names.pragma), TokenFlags::EMPTY, span));
1209 for (at, t) in tokens.into_iter().filter(|t| !t.is_eof()).enumerate() {
1213 let spaced = at == 0 || t.flags.has(TokenFlags::LEADING_SPACE);
1216 let flags = if spaced {
1217 TokenFlags::EMPTY.with(TokenFlags::LEADING_SPACE)
1218 } else {
1219 TokenFlags::EMPTY
1220 };
1221 out.push(Tok::synthetic(t.kind, t.value, flags, span));
1222 }
1223 }
1224}
1225
1226fn guard_opener(body: &[PpToken], names: &Names) -> Option<Symbol> {
1231 let name = ident_of(body.first()?)?;
1232 let rest = &body[1..];
1233 if name == names.ifndef {
1234 let [only] = rest else {
1235 return None;
1236 };
1237 return ident_of(only);
1238 }
1239 if name != names.r#if {
1240 return None;
1241 }
1242 let [bang, defined, tail @ ..] = rest else {
1243 return None;
1244 };
1245 if bang.punct() != Some(Punct::Bang) || ident_of(defined) != Some(names.defined) {
1246 return None;
1247 }
1248 match tail {
1249 [only] => ident_of(only),
1250 [open, only, close]
1251 if open.punct() == Some(Punct::LParen) && close.punct() == Some(Punct::RParen) =>
1252 {
1253 ident_of(only)
1254 }
1255 _ => None,
1256 }
1257}
1258
1259fn is_include(name: Option<Symbol>, names: &Names) -> bool {
1261 name == Some(names.include) || name == Some(names.include_next) || name == Some(names.embed)
1262}
1263
1264fn is_directive(tok: PpToken) -> bool {
1266 tok.flags.has(TokenFlags::START_OF_LINE) && tok.punct() == Some(Punct::Hash)
1267}
1268
1269fn ident_of(tok: &PpToken) -> Option<Symbol> {
1270 match tok.kind {
1271 PpTokenKind::Ident => tok.value,
1272 _ => None,
1273 }
1274}
1275
1276fn last_span(tokens: &[PpToken]) -> Span {
1277 tokens.last().map_or(Span::DUMMY, |t| t.span)
1278}
1279
1280fn number(value: bool, flags: TokenFlags, span: Span, interner: &mut Interner) -> Tok {
1282 let sym = interner.intern(if value { "1" } else { "0" });
1283 Tok::synthetic(PpTokenKind::Number, Some(sym), flags, span)
1284}
1285
1286fn spell_line(tokens: &[PpToken], interner: &Interner) -> String {
1288 let mut out = String::new();
1289 for (index, tok) in tokens.iter().enumerate() {
1290 if index > 0 && tok.flags.has(TokenFlags::LEADING_SPACE) {
1291 out.push(' ');
1292 }
1293 match tok.value {
1294 Some(sym) => out.push_str(interner.resolve(sym)),
1295 None => {
1296 if let Some(p) = tok.punct() {
1297 out.push_str(p.as_str());
1298 }
1299 }
1300 }
1301 }
1302 out
1303}
1304
1305fn destringize(literal: &str) -> String {
1310 let body = literal
1311 .trim_start_matches(['L', 'u', 'U', '8'])
1312 .strip_prefix('"')
1313 .and_then(|s| s.strip_suffix('"'))
1314 .unwrap_or(literal);
1315 let mut out = String::with_capacity(body.len());
1316 let mut chars = body.chars();
1317 while let Some(c) = chars.next() {
1318 if c != '\\' {
1319 out.push(c);
1320 continue;
1321 }
1322 match chars.next() {
1323 Some('"') => out.push('"'),
1324 Some('\\') => out.push('\\'),
1325 Some(other) => {
1326 out.push('\\');
1327 out.push(other);
1328 }
1329 None => out.push('\\'),
1330 }
1331 }
1332 out
1333}
1334
1335fn arguments(line: &[Tok], at: usize) -> Option<(&[Tok], usize)> {
1341 if !line.get(at)?.is(Punct::LParen) {
1342 return None;
1343 }
1344 let mut depth = 1u32;
1345 let mut end = at + 1;
1346 while end < line.len() {
1347 if line[end].is(Punct::LParen) {
1348 depth += 1;
1349 } else if line[end].is(Punct::RParen) {
1350 depth -= 1;
1351 if depth == 0 {
1352 return Some((&line[at + 1..end], end + 1));
1353 }
1354 }
1355 end += 1;
1356 }
1357 None
1358}
1359
1360fn attribute_name<'i>(operand: &[Tok], interner: &'i Interner) -> Option<&'i str> {
1366 let name = match operand {
1367 [one] => one,
1368 [_, scope, name] if scope.is(Punct::ColonColon) => name,
1369 _ => return None,
1370 };
1371 name.ident().map(|sym| interner.resolve(sym))
1372}
1373
1374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1381enum Pass {
1382 Headers,
1384 Rest,
1387 Text,
1389}
1390
1391impl Pass {
1392 fn answers(self, op: Op) -> bool {
1394 match self {
1395 Pass::Headers => op.is_header(),
1396 Pass::Rest => true,
1397 Pass::Text => !op.is_header(),
1398 }
1399 }
1400}
1401
1402#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1404enum Op {
1405 Include,
1407 IncludeNext,
1409 Embed,
1412 BuildingModule,
1414 Table(Kind),
1416}
1417
1418impl Op {
1419 fn is_header(self) -> bool {
1421 matches!(self, Op::Include | Op::IncludeNext | Op::Embed)
1422 }
1423}
1424
1425struct HasOps {
1430 ops: [(Symbol, Op); 9],
1431 range: (Symbol, Symbol),
1438}
1439
1440impl HasOps {
1441 fn new(interner: &mut Interner) -> HasOps {
1442 let ops = [
1443 (interner.intern("__has_include"), Op::Include),
1444 (interner.intern("__has_include_next"), Op::IncludeNext),
1445 (interner.intern("__has_embed"), Op::Embed),
1446 (interner.intern("__has_attribute"), Op::Table(Kind::Attribute)),
1447 (interner.intern("__has_c_attribute"), Op::Table(Kind::CAttribute)),
1448 (interner.intern("__has_builtin"), Op::Table(Kind::Builtin)),
1449 (interner.intern("__has_feature"), Op::Table(Kind::Feature)),
1450 (interner.intern("__has_extension"), Op::Table(Kind::Extension)),
1451 (interner.intern("__building_module"), Op::BuildingModule),
1452 ];
1453 let mut range = (ops[0].0, ops[0].0);
1454 for &(sym, _) in &ops {
1455 range = (range.0.min(sym), range.1.max(sym));
1456 }
1457 HasOps { ops, range }
1458 }
1459
1460 #[inline]
1462 fn op(&self, name: Symbol) -> Option<Op> {
1463 if name < self.range.0 || name > self.range.1 {
1464 return None;
1465 }
1466 self.ops.iter().find(|(sym, _)| *sym == name).map(|(_, op)| *op)
1467 }
1468}
1469
1470struct Names {
1476 define: Symbol,
1477 undef: Symbol,
1478 r#if: Symbol,
1479 ifdef: Symbol,
1480 ifndef: Symbol,
1481 elif: Symbol,
1482 elifdef: Symbol,
1483 elifndef: Symbol,
1484 r#else: Symbol,
1485 endif: Symbol,
1486 line: Symbol,
1487 error: Symbol,
1488 warning: Symbol,
1489 pragma: Symbol,
1490 include: Symbol,
1491 include_next: Symbol,
1492 embed: Symbol,
1493 defined: Symbol,
1494 once: Symbol,
1495 pragma_op: Symbol,
1496 has: HasOps,
1497}
1498
1499impl Names {
1500 fn new(interner: &mut Interner) -> Names {
1501 Names {
1502 define: interner.intern("define"),
1503 undef: interner.intern("undef"),
1504 r#if: interner.intern("if"),
1505 ifdef: interner.intern("ifdef"),
1506 ifndef: interner.intern("ifndef"),
1507 elif: interner.intern("elif"),
1508 elifdef: interner.intern("elifdef"),
1509 elifndef: interner.intern("elifndef"),
1510 r#else: interner.intern("else"),
1511 endif: interner.intern("endif"),
1512 line: interner.intern("line"),
1513 error: interner.intern("error"),
1514 warning: interner.intern("warning"),
1515 pragma: interner.intern("pragma"),
1516 include: interner.intern("include"),
1517 include_next: interner.intern("include_next"),
1518 embed: interner.intern("embed"),
1519 defined: interner.intern("defined"),
1520 once: interner.intern("once"),
1521 pragma_op: interner.intern("_Pragma"),
1522 has: HasOps::new(interner),
1523 }
1524 }
1525}
1526
1527#[cfg(test)]
1528mod tests {
1529 use rucc_diag::{Severity, SourceMap};
1530 use rucc_session::{MemoryFileSystem, SearchPath};
1531
1532 use super::*;
1533 use rucc_session::Std;
1534
1535 use crate::predef::Timestamp;
1536
1537 struct Run {
1542 interner: Interner,
1543 sources: SourceMap,
1544 fs: MemoryFileSystem,
1545 search: SearchPath,
1546 pp: Preprocessor,
1547 }
1548
1549 impl Run {
1550 fn new() -> Run {
1551 Run {
1552 interner: Interner::new(),
1553 sources: SourceMap::new(),
1554 fs: MemoryFileSystem::new(),
1555 search: SearchPath::new(),
1556 pp: Preprocessor::new(),
1557 }
1558 }
1559
1560 fn file(&mut self, path: &str, contents: &str) {
1562 self.fs.insert(path, contents.as_bytes().to_vec());
1563 }
1564
1565 fn bytes(&mut self, path: &str, contents: &[u8]) {
1568 self.fs.insert(path, contents.to_vec());
1569 }
1570
1571 fn dir(&mut self, path: &str) {
1573 self.search.push_bracket(path);
1574 }
1575
1576 fn predefine(&mut self, triple: &str, opts: &Predef) {
1578 let target = TargetInfo::new(triple.parse().expect("a supported triple"));
1579 let mut cx =
1580 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1581 self.pp.predefine(&target, opts, &mut cx).expect("the map has room");
1582 }
1583
1584 fn go(&mut self, src: &str) -> String {
1586 self.go_named("/main.c", src)
1587 }
1588
1589 fn raw(&mut self, src: &str) -> Vec<Tok> {
1591 let file = self.sources.add("/main.c", src.as_bytes().to_vec()).expect("room");
1592 let mut cx =
1593 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1594 self.pp.run(file, &mut cx)
1595 }
1596
1597 fn go_named(&mut self, path: &str, src: &str) -> String {
1599 let file = self.sources.add(path, src.as_bytes().to_vec()).expect("the map has room");
1600 let out = {
1601 let mut cx =
1602 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1603 self.pp.run(file, &mut cx)
1604 };
1605 let mut text = String::new();
1606 for (at, tok) in out.iter().enumerate() {
1607 let spaced = tok.flags.has(TokenFlags::LEADING_SPACE)
1608 || tok.flags.has(TokenFlags::START_OF_LINE);
1609 if at > 0 && spaced {
1610 text.push(' ');
1611 }
1612 match tok.kind {
1613 PpTokenKind::Punct(p) => text.push_str(p.as_str()),
1614 _ => text.push_str(
1615 self.interner.resolve(tok.value.expect("every non-punctuator interns")),
1616 ),
1617 }
1618 }
1619 text
1620 }
1621
1622 fn files(&self) -> usize {
1626 self.sources.files().len()
1627 }
1628
1629 fn messages(&mut self) -> Vec<String> {
1630 self.pp.take_diagnostics().into_iter().map(|d| d.message).collect()
1631 }
1632
1633 fn severities(&mut self) -> Vec<Severity> {
1634 self.pp.diagnostics().iter().map(|d| d.severity).collect()
1635 }
1636 }
1637
1638 fn clean(src: &str) -> String {
1639 let mut run = Run::new();
1640 let text = run.go(src);
1641 assert!(run.messages().is_empty(), "expected no diagnostics from {src:?}");
1642 text
1643 }
1644
1645 #[test]
1646 fn a_taken_branch_is_kept_and_the_other_is_not() {
1647 assert_eq!(clean("#if 1\nyes\n#else\nno\n#endif\n"), "yes");
1648 assert_eq!(clean("#if 0\nyes\n#else\nno\n#endif\n"), "no");
1649 }
1650
1651 #[test]
1652 fn ifdef_and_ifndef_ask_the_macro_table() {
1653 assert_eq!(clean("#define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
1654 assert_eq!(clean("#ifdef F\nyes\n#endif\n"), "");
1655 assert_eq!(clean("#ifndef F\nyes\n#endif\n"), "yes");
1656 assert_eq!(clean("#define F 1\n#if 0\na\n#elifdef F\nb\n#endif\n"), "b");
1658 assert_eq!(clean("#if 0\na\n#elifndef F\nb\n#endif\n"), "b");
1659 }
1660
1661 #[test]
1662 fn only_the_first_true_branch_of_a_chain_is_taken() {
1663 assert_eq!(clean("#if 0\na\n#elif 1\nb\n#elif 1\nc\n#else\nd\n#endif\n"), "b");
1664 assert_eq!(clean("#if 0\na\n#elif 0\nb\n#else\nc\n#endif\n"), "c");
1665 }
1666
1667 #[test]
1668 fn a_branch_after_one_that_was_taken_is_not_evaluated() {
1669 assert_eq!(clean("#if 1\na\n#elif 1/0\nb\n#endif\n"), "a");
1672 }
1673
1674 #[test]
1675 fn a_skipped_region_is_not_read_for_anything_but_nesting() {
1676 let src = "#if 0\nthis is not C at all\n#frobnicate\n#define\n#if 1\ninner\n#endif\n#endif\nafter\n";
1678 assert_eq!(clean(src), "after");
1679 }
1680
1681 #[test]
1682 fn nesting_inside_a_dead_branch_stays_balanced() {
1683 let src = "#if 0\n#ifdef X\na\n#else\nb\n#endif\n#else\nc\n#endif\n";
1684 assert_eq!(clean(src), "c");
1685 }
1686
1687 #[test]
1688 fn defined_works_in_both_spellings_and_before_expansion() {
1689 assert_eq!(clean("#define F 0\n#if defined F\nyes\n#endif\n"), "yes");
1690 assert_eq!(clean("#define F 0\n#if defined(F)\nyes\n#endif\n"), "yes");
1691 assert_eq!(clean("#if defined(F)\nyes\n#endif\n"), "");
1692 assert_eq!(clean("#define F 0\n#if defined F && !F\nyes\n#endif\n"), "yes");
1695 }
1696
1697 #[test]
1698 fn an_identifier_that_survived_expansion_is_zero() {
1699 assert_eq!(clean("#if NOT_DEFINED_ANYWHERE\nyes\n#else\nno\n#endif\n"), "no");
1700 assert_eq!(clean("#if !NOT_DEFINED_ANYWHERE\nyes\n#endif\n"), "yes");
1701 }
1702
1703 #[test]
1704 fn short_circuiting_keeps_a_guarded_expression_safe() {
1705 assert_eq!(clean("#if defined(F) && 1/F\nyes\n#else\nno\n#endif\n"), "no");
1708 assert_eq!(clean("#if 1 ? 2 : 1/0\nyes\n#endif\n"), "yes");
1709 }
1710
1711 #[test]
1712 fn the_operators_have_the_precedence_they_do_in_c() {
1713 assert_eq!(clean("#if 1 + 2 * 3 == 7\nyes\n#endif\n"), "yes");
1714 assert_eq!(clean("#if (1 + 2) * 3 == 9\nyes\n#endif\n"), "yes");
1715 assert_eq!(clean("#if 1 << 4 == 16\nyes\n#endif\n"), "yes");
1716 assert_eq!(clean("#if -8 / 3 == -2\nyes\n#endif\n"), "yes");
1717 assert_eq!(clean("#if (0xff & 0x0f) == 15\nyes\n#endif\n"), "yes");
1718 }
1719
1720 #[test]
1721 fn an_unsigned_operand_makes_the_whole_comparison_unsigned() {
1722 assert_eq!(clean("#if -1 < 0u\nyes\n#else\nno\n#endif\n"), "no");
1726 assert_eq!(clean("#if -1 < 0\nyes\n#else\nno\n#endif\n"), "yes");
1727 }
1728
1729 #[test]
1730 fn character_constants_evaluate() {
1731 assert_eq!(clean("#if 'A' == 65\nyes\n#endif\n"), "yes");
1732 assert_eq!(clean("#if '\\n' == 10\nyes\n#endif\n"), "yes");
1733 }
1734
1735 #[test]
1736 fn a_macro_is_expanded_before_the_expression_is_evaluated() {
1737 assert_eq!(clean("#define V 3\n#if V > 2\nyes\n#endif\n"), "yes");
1738 assert_eq!(clean("#define M(a) ((a) * 2)\n#if M(3) == 6\nyes\n#endif\n"), "yes");
1739 }
1740
1741 #[test]
1742 fn an_invocation_may_span_lines_within_a_run_of_text() {
1743 assert_eq!(clean("#define M(a, b) a + b\nM(1,\n2)\n"), "1 + 2");
1744 }
1745
1746 #[test]
1747 fn undef_removes_a_definition() {
1748 assert_eq!(clean("#define F 1\n#undef F\n#ifdef F\nyes\n#else\nno\n#endif\n"), "no");
1749 assert_eq!(clean("#undef NEVER_DEFINED\nok\n"), "ok");
1752 }
1753
1754 #[test]
1755 fn some_names_cannot_be_undefined() {
1756 let mut run = Run::new();
1757 run.go("#undef defined\n");
1758 assert_eq!(run.messages(), vec!["`defined` cannot be undefined".to_owned()]);
1759 }
1760
1761 #[test]
1762 fn error_reports_the_rest_of_the_line() {
1763 let mut run = Run::new();
1764 run.go("#if 0\n#error not this one\n#else\n#error unsupported target\n#endif\n");
1765 assert_eq!(run.messages(), vec!["unsupported target".to_owned()]);
1766 }
1767
1768 #[test]
1769 fn warning_is_a_warning() {
1770 let mut run = Run::new();
1771 run.go("#warning this is fine\n");
1772 assert_eq!(run.severities(), vec![Severity::Warning]);
1773 assert_eq!(run.messages(), vec!["this is fine".to_owned()]);
1774 }
1775
1776 #[test]
1777 fn an_unterminated_conditional_is_reported() {
1778 let mut run = Run::new();
1779 assert_eq!(run.go("#if 1\nyes\n"), "yes");
1780 assert_eq!(run.messages(), vec!["unterminated `#if`".to_owned()]);
1781 }
1782
1783 #[test]
1784 fn a_conditional_without_an_if_is_reported() {
1785 let mut run = Run::new();
1786 run.go("#endif\n");
1787 assert_eq!(run.messages(), vec!["`#endif` without `#if`".to_owned()]);
1788
1789 let mut run = Run::new();
1790 run.go("#if 1\n#else\n#else\n#endif\n");
1791 assert_eq!(run.messages(), vec!["a second `#else`".to_owned()]);
1792
1793 let mut run = Run::new();
1794 run.go("#if 1\n#else\n#elif 1\n#endif\n");
1795 assert_eq!(run.messages(), vec!["`#elif` after `#else`".to_owned()]);
1796 }
1797
1798 #[test]
1799 fn tokens_after_endif_are_a_warning_rather_than_an_error() {
1800 let mut run = Run::new();
1803 assert_eq!(run.go("#if 1\nyes\n#endif FOO\n"), "yes");
1804 assert_eq!(run.severities(), vec![Severity::Warning]);
1805 assert_eq!(run.messages(), vec!["extra tokens after `#endif`".to_owned()]);
1806 }
1807
1808 #[test]
1809 fn the_null_directive_does_nothing() {
1810 assert_eq!(clean("#\na\n#\nb\n"), "a b");
1811 }
1812
1813 #[test]
1814 fn an_unknown_directive_is_an_error_when_the_region_is_live() {
1815 let mut run = Run::new();
1816 run.go("#frobnicate\n");
1817 assert_eq!(run.messages(), vec!["invalid preprocessing directive".to_owned()]);
1818 }
1819
1820 #[test]
1821 fn line_is_recorded_for_the_source_map() {
1822 let mut run = Run::new();
1823 run.go("#line 42 \"other.c\"\n");
1824 assert!(run.messages().is_empty());
1825 let recorded = run.pp.line_directives();
1826 assert_eq!(recorded.len(), 1);
1827 assert_eq!(recorded[0].line, 42);
1828 let file = recorded[0].file.expect("a file name was given");
1829 assert_eq!(run.interner.resolve(file), "\"other.c\"");
1830 }
1831
1832 #[test]
1833 fn a_line_number_out_of_range_is_refused() {
1834 let mut run = Run::new();
1835 run.go("#line 0\n");
1836 assert_eq!(run.messages(), vec!["`#line` number is out of range".to_owned()]);
1837
1838 let mut run = Run::new();
1839 run.go("#line notanumber\n");
1840 assert_eq!(run.messages(), vec!["`#line` needs a decimal line number".to_owned()]);
1841 }
1842
1843 #[test]
1844 fn a_pragma_passes_through_unchanged() {
1845 assert_eq!(clean("#pragma pack(1)\nint x;\n"), "#pragma pack(1) int x;");
1846 }
1847
1848 #[test]
1853 fn the_space_between_the_hash_and_the_word_comes_off_a_pragma_that_is_indented() {
1854 assert_eq!(clean("#if 1\n# pragma pack(1)\n#endif\n"), "#pragma pack(1)");
1855 assert_eq!(clean("# pragma pack( 1 )\n"), "#pragma pack( 1 )", "the rest is kept");
1856 }
1857
1858 #[test]
1859 fn the_pragma_operator_becomes_a_pragma() {
1860 assert_eq!(
1861 clean("_Pragma(\"GCC visibility push(default)\")\nint x;\n"),
1862 "#pragma GCC visibility push(default) int x;"
1863 );
1864 }
1865
1866 #[test]
1867 fn the_pragma_operator_works_from_inside_a_macro() {
1868 let src = "#define PUSH _Pragma(\"pack(push)\")\nPUSH\nint x;\n";
1871 assert_eq!(clean(src), "#pragma pack(push) int x;");
1872 }
1873
1874 #[test]
1878 fn what_follows_a_pragma_operator_starts_a_line() {
1879 let mut run = Run::new();
1880 let out = run.raw("int x; _Pragma(\"pack(1)\") int y;\n");
1881 let starts: Vec<_> =
1882 out.iter().map(|tok| tok.flags.has(TokenFlags::START_OF_LINE)).collect();
1883 assert_eq!(
1886 starts,
1887 vec![true, false, false, true, false, false, false, false, false, true, false, false]
1888 );
1889 }
1890
1891 #[test]
1892 fn a_pragma_operator_that_is_not_given_a_string_is_reported() {
1893 let mut run = Run::new();
1894 run.go("_Pragma(x)\n");
1895 assert_eq!(run.messages(), vec!["`_Pragma` takes a single string literal".to_owned()]);
1896 }
1897
1898 #[test]
1899 fn an_include_reads_the_file_it_names() {
1900 let mut run = Run::new();
1901 run.file("/dir/one.h", "int from_the_header;\n");
1902 run.dir("/dir");
1903 assert_eq!(run.go("#include <one.h>\nint after;\n"), "int from_the_header; int after;");
1904 assert!(run.messages().is_empty());
1905 }
1906
1907 #[test]
1908 fn a_quoted_include_looks_next_to_the_including_file_first() {
1909 let mut run = Run::new();
1910 run.file("/local.h", "beside\n");
1911 run.file("/dir/local.h", "on the path\n");
1912 run.dir("/dir");
1913 assert_eq!(run.go("#include \"local.h\"\n"), "beside");
1914 assert!(run.messages().is_empty());
1915 }
1916
1917 #[test]
1918 fn an_angled_include_does_not_look_next_to_the_including_file() {
1919 let mut run = Run::new();
1920 run.file("/local.h", "beside\n");
1921 run.file("/dir/local.h", "on the path\n");
1922 run.dir("/dir");
1923 assert_eq!(run.go("#include <local.h>\n"), "on the path");
1924 }
1925
1926 #[test]
1927 fn a_macro_defined_in_a_header_is_visible_after_the_include() {
1928 let mut run = Run::new();
1929 run.file("/dir/defs.h", "#define N 42\n");
1930 run.dir("/dir");
1931 assert_eq!(run.go("#include <defs.h>\nint a = N;\n"), "int a = 42;");
1932 assert!(run.messages().is_empty());
1933 }
1934
1935 #[test]
1936 fn an_include_guard_keeps_the_second_read_empty() {
1937 let mut run = Run::new();
1938 run.file("/dir/g.h", "#ifndef G\n#define G\nonce\n#endif\n");
1939 run.dir("/dir");
1940 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
1941 assert!(run.messages().is_empty());
1942 assert_eq!(run.files(), 2, "the second include is not opened at all");
1943 }
1944
1945 #[test]
1946 fn the_other_spelling_of_a_guard_is_recognised_too() {
1947 for guard in ["#if !defined(G)", "#if !defined G"] {
1948 let mut run = Run::new();
1949 run.file("/dir/g.h", &format!("{guard}\n#define G\nonce\n#endif\n"));
1950 run.dir("/dir");
1951 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
1952 assert_eq!(run.files(), 2, "{guard} should be a guard");
1953 }
1954 }
1955
1956 #[test]
1957 fn a_conditional_that_is_not_a_guard_does_not_skip_anything() {
1958 let mut run = Run::new();
1961 run.file("/dir/g.h", "#ifndef G\ntwice\n#endif\n");
1962 run.dir("/dir");
1963 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "twice twice");
1964 assert_eq!(run.files(), 3);
1965 }
1966
1967 #[test]
1968 fn a_token_outside_the_guard_stops_it_being_a_guard() {
1969 let mut run = Run::new();
1970 run.file("/dir/g.h", "#ifndef G\n#define G\n#endif\nalways\n");
1971 run.dir("/dir");
1972 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "always always");
1973 assert_eq!(run.files(), 3);
1974 }
1975
1976 #[test]
1977 fn pragma_once_skips_the_second_read_and_does_not_reach_the_output() {
1978 let mut run = Run::new();
1979 run.file("/dir/o.h", "#pragma once\nonce\n");
1980 run.dir("/dir");
1981 assert_eq!(run.go("#include <o.h>\n#include <o.h>\n"), "once");
1982 assert!(run.messages().is_empty());
1983 assert_eq!(run.files(), 2);
1984 }
1985
1986 #[test]
1987 fn pragma_once_in_the_main_file_is_a_warning() {
1988 let mut run = Run::new();
1991 assert_eq!(run.go("#pragma once\nx\n"), "x");
1992 assert_eq!(run.severities(), vec![Severity::Warning]);
1993 assert_eq!(run.messages(), vec!["`#pragma once` in the main file".to_owned()]);
1994 }
1995
1996 #[test]
1997 fn any_other_pragma_still_passes_through() {
1998 assert_eq!(clean("#pragma once_upon_a_time\n"), "#pragma once_upon_a_time");
1999 }
2000
2001 #[test]
2002 fn has_include_answers_from_the_search_path() {
2003 let mut run = Run::new();
2004 run.file("/dir/there.h", "");
2005 run.dir("/dir");
2006 let src = "#if __has_include(<there.h>)\nyes\n#endif\n\
2007 #if __has_include(<gone.h>)\nno\n#endif\n";
2008 assert_eq!(run.go(src), "yes");
2009 assert!(run.messages().is_empty(), "a header that is not there is an answer, not an error");
2010 }
2011
2012 #[test]
2013 fn has_include_asks_the_question_the_include_on_the_same_line_would() {
2014 let mut run = Run::new();
2018 run.file("/beside.h", "");
2019 let src = "#if __has_include(\"beside.h\")\nquoted\n#endif\n\
2020 #if __has_include(<beside.h>)\nangled\n#endif\n";
2021 assert_eq!(run.go(src), "quoted");
2022 }
2023
2024 #[test]
2025 fn has_include_next_starts_where_include_next_would() {
2026 let mut run = Run::new();
2027 run.file("/a/both.h", "#if __has_include_next(<both.h>)\nmore\n#endif\n");
2028 run.file("/b/both.h", "last\n");
2029 run.file("/a/only.h", "#if __has_include_next(<only.h>)\nmore\n#endif\n");
2030 run.dir("/a");
2031 run.dir("/b");
2032 assert_eq!(run.go("#include <both.h>\n"), "more");
2033 assert_eq!(run.go("#include <only.h>\n"), "", "there is nothing after /a to find it in");
2034 }
2035
2036 #[test]
2037 fn the_operand_of_has_include_is_not_macro_expanded() {
2038 let mut run = Run::new();
2041 run.file("/dir/linux/version.h", "");
2042 run.dir("/dir");
2043 let src = "#define linux 1\n#if __has_include(<linux/version.h>)\nyes\n#endif\n";
2044 assert_eq!(run.go(src), "yes");
2045 }
2046
2047 #[test]
2048 fn a_macro_may_expand_to_a_has_include() {
2049 let mut run = Run::new();
2051 run.file("/dir/there.h", "");
2052 run.dir("/dir");
2053 let src = "#define HAVE __has_include(<there.h>)\n#if HAVE\nyes\n#endif\n";
2054 assert_eq!(run.go(src), "yes");
2055 }
2056
2057 #[test]
2058 fn defined_says_the_has_operators_are_there() {
2059 let src = "#if defined(__has_include) && defined __has_builtin\nyes\n#endif\n";
2062 assert_eq!(clean(src), "yes");
2063 assert_eq!(clean("#ifdef __has_attribute\nyes\n#endif\n"), "yes");
2064 }
2065
2066 #[test]
2067 fn has_attribute_answers_out_of_the_matrix() {
2068 assert_eq!(clean("#if __has_attribute(packed)\nyes\n#endif\n"), "");
2071 assert_eq!(clean("#if __has_attribute(no_such_attribute)\nyes\n#endif\n"), "");
2072 assert_eq!(clean("#if !__has_attribute(packed)\nno\n#endif\n"), "no");
2073 }
2074
2075 #[test]
2076 fn the_scoped_spelling_of_an_attribute_is_the_same_question() {
2077 assert_eq!(clean("#if __has_c_attribute(gnu::packed)\nyes\n#endif\n"), "");
2081 assert_eq!(clean("#if __has_c_attribute(deprecated)\nyes\n#endif\n"), "");
2082 }
2083
2084 #[test]
2085 fn has_builtin_answers_no_until_the_builtin_is_real() {
2086 assert_eq!(clean("#if __has_builtin(__builtin_expect)\nyes\n#endif\n"), "");
2087 assert_eq!(clean("#if __has_builtin(__builtin_nonesuch)\nyes\n#endif\n"), "");
2088 }
2089
2090 #[test]
2091 fn has_feature_and_has_extension_read_the_same_table() {
2092 assert_eq!(clean("#if __has_feature(pragma_once)\nyes\n#endif\n"), "yes");
2095 assert_eq!(clean("#if __has_extension(pragma_once)\nyes\n#endif\n"), "yes");
2096 assert_eq!(clean("#if __has_extension(include_next)\nyes\n#endif\n"), "yes");
2097 assert_eq!(clean("#if __has_feature(include_next)\nyes\n#endif\n"), "");
2098 assert_eq!(clean("#if __has_feature(statement_expressions)\nyes\n#endif\n"), "");
2099 }
2100
2101 #[test]
2102 fn building_module_is_always_no_and_is_recognised_so_that_the_line_parses() {
2103 assert_eq!(clean("#if __building_module(m)\nyes\n#endif\n"), "");
2107 assert_eq!(clean("#if !__building_module(m)\nyes\n#endif\n"), "yes");
2108 assert_eq!(
2109 clean(
2110 "#if !defined(offsetof) || (__has_feature(modules) && !__building_module(x))\nyes\n#endif\n"
2111 ),
2112 "yes"
2113 );
2114 assert_eq!(clean("#ifdef __building_module\nyes\n#endif\n"), "yes");
2116 assert_eq!(clean("#if defined(__building_module)\nyes\n#endif\n"), "yes");
2117 }
2118
2119 #[test]
2120 fn a_has_operator_without_an_operand_is_reported() {
2121 let mut run = Run::new();
2122 run.go("#if __has_include\nyes\n#endif\n");
2123 assert_eq!(run.messages(), ["expected `(` after `__has_include`"]);
2124 let mut run = Run::new();
2125 run.go("#if __has_include(1)\nyes\n#endif\n");
2126 assert_eq!(run.messages(), ["expected a file name in `<>` or `\"\"`"]);
2127 let mut run = Run::new();
2128 run.go("#if __has_attribute(\"packed\")\nyes\n#endif\n");
2129 assert_eq!(run.messages(), ["expected an identifier as the operand of `__has_attribute`"]);
2130 }
2131
2132 #[test]
2133 fn the_has_operators_answer_in_ordinary_text_too() {
2134 assert_eq!(clean("f __has_feature(pragma_once)\n"), "f 1");
2138 assert_eq!(clean("b __has_builtin(__builtin_expect)\n"), "b 0");
2139 assert_eq!(clean("a __has_attribute(packed)\n"), "a 0");
2140 assert_eq!(clean("c __has_c_attribute(deprecated)\n"), "c 0");
2141 assert_eq!(clean("m __building_module(foo)\n"), "m 0");
2142 }
2143
2144 #[test]
2145 fn a_macro_that_expands_to_a_has_operator_is_answered_where_it_is_used() {
2146 assert_eq!(clean("#define HAVE __has_feature(pragma_once)\nx HAVE\n"), "x 1");
2149 assert_eq!(clean("#define HAVE(x) __has_attribute(x)\ny HAVE(packed)\n"), "y 0");
2150 }
2151
2152 #[test]
2153 fn a_has_operator_in_text_still_needs_its_operand() {
2154 let mut run = Run::new();
2155 run.go("tail __has_attribute;\n");
2156 assert_eq!(run.messages(), ["expected `(` after `__has_attribute`"]);
2157 }
2158
2159 #[test]
2160 fn the_header_operators_are_refused_in_ordinary_text() {
2161 let mut run = Run::new();
2164 run.file("/dir/there.h", "");
2165 run.dir("/dir");
2166 run.go("a __has_include(<there.h>)\n");
2167 assert_eq!(run.messages(), ["`__has_include` used outside of a preprocessing directive"]);
2168 let mut run = Run::new();
2169 run.go("b __has_include_next(\"x.h\")\n");
2170 assert_eq!(
2171 run.messages(),
2172 ["`__has_include_next` used outside of a preprocessing directive"]
2173 );
2174 }
2175
2176 #[test]
2177 fn the_predefined_set_is_visible_to_the_source_file() {
2178 let mut run = Run::new();
2179 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2180 let src = "#if defined(__x86_64__) && defined(__linux__) && __SIZEOF_LONG__ == 8\n\
2181 yes\n#endif\n";
2182 assert_eq!(run.go(src), "yes");
2183 assert!(run.messages().is_empty());
2184 }
2185
2186 #[test]
2187 fn the_predefined_set_follows_the_target_and_not_the_host() {
2188 let mut run = Run::new();
2189 run.predefine("aarch64-unknown-linux-gnu", &Predef::new());
2190 assert_eq!(
2191 run.go("#ifdef __x86_64__\nno\n#endif\n#ifdef __aarch64__\nyes\n#endif\n"),
2192 "yes"
2193 );
2194 }
2195
2196 #[test]
2197 fn a_predefined_macro_expands_where_it_is_used() {
2198 let mut run = Run::new();
2199 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2200 assert_eq!(run.go("__SIZE_TYPE__ n;\n"), "long unsigned int n;");
2201 }
2202
2203 #[test]
2204 fn a_command_line_define_is_a_definition_like_any_other() {
2205 let mut opts = Predef::new();
2206 opts.defines = vec!["FOO".to_owned(), "BAR=3".to_owned()];
2207 opts.undefines = vec!["__linux__".to_owned()];
2208 let mut run = Run::new();
2209 run.predefine("x86_64-unknown-linux-gnu", &opts);
2210 let src = "#if FOO && BAR == 3 && !defined(__linux__)\nyes\n#endif\n";
2211 assert_eq!(run.go(src), "yes");
2212 assert!(run.messages().is_empty());
2213 }
2214
2215 #[test]
2216 fn the_predefined_set_produces_no_tokens_of_its_own() {
2217 let mut run = Run::new();
2220 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2221 assert_eq!(run.go("alone\n"), "alone");
2222 }
2223
2224 #[test]
2225 fn the_predefined_files_are_named_the_way_gcc_names_them() {
2226 let mut run = Run::new();
2227 let mut opts = Predef::new();
2228 opts.defines = vec!["FOO=1".to_owned()];
2229 run.predefine("x86_64-unknown-linux-gnu", &opts);
2230 let names: Vec<&str> = run.sources.files().iter().map(|f| f.name.as_str()).collect();
2231 assert_eq!(names, ["<built-in>", "<command-line>"]);
2232 }
2233
2234 #[test]
2235 fn a_dialect_without_the_gnu_extensions_says_so() {
2236 let mut opts = Predef::new();
2237 opts.gnu_extensions = false;
2238 opts.std = Std::C99;
2239 let mut run = Run::new();
2240 run.predefine("x86_64-unknown-linux-gnu", &opts);
2241 let src = "#if defined(__STRICT_ANSI__) && __STDC_VERSION__ == 199901L && !defined(linux)\n\
2242 yes\n#endif\n";
2243 assert_eq!(run.go(src), "yes");
2244 }
2245
2246 #[test]
2247 fn the_date_and_time_are_the_same_for_the_whole_translation_unit() {
2248 let mut opts = Predef::new();
2249 opts.timestamp = Timestamp::from_unix(0);
2250 let mut run = Run::new();
2251 run.predefine("x86_64-unknown-linux-gnu", &opts);
2252 assert_eq!(run.go("__DATE__ __TIME__\n"), "\"Jan 1 1970\" \"00:00:00\"");
2253 }
2254
2255 #[test]
2256 fn a_has_operator_in_a_dead_branch_is_not_asked_about() {
2257 assert_eq!(clean("#if 0\n#if __has_include\n#endif\n#endif\nafter\n"), "after");
2259 }
2260
2261 #[test]
2262 fn a_conditional_may_not_span_an_include() {
2263 let mut run = Run::new();
2267 run.file("/dir/open.h", "#if 1\n");
2268 run.dir("/dir");
2269 run.go("#include <open.h>\nkept\n#endif\n");
2270 let messages = run.messages();
2271 assert_eq!(messages.len(), 2);
2272 assert!(messages[0].contains("unterminated"));
2273 assert!(messages[1].contains("without"));
2274 }
2275
2276 #[test]
2277 fn include_next_continues_after_the_directory_the_file_came_from() {
2278 let mut run = Run::new();
2281 run.file("/a/limits.h", "wrapper\n#include_next <limits.h>\n");
2282 run.file("/b/limits.h", "real\n");
2283 run.dir("/a");
2284 run.dir("/b");
2285 assert_eq!(run.go("#include <limits.h>\n"), "wrapper real");
2286 assert!(run.messages().is_empty());
2287 }
2288
2289 #[test]
2290 fn a_computed_include_is_expanded_first() {
2291 let mut run = Run::new();
2292 run.file("/dir/sub/thing.h", "computed\n");
2293 run.dir("/dir");
2294 let src = "#define HEADER <sub/thing.h>\n#include HEADER\n";
2295 assert_eq!(run.go(src), "computed");
2296 assert!(run.messages().is_empty());
2297 let mut run = Run::new();
2299 run.file("/dir/sub/thing.h", "computed\n");
2300 run.dir("/dir");
2301 assert_eq!(run.go("#define H \"sub/thing.h\"\n#include H\n"), "computed");
2302 }
2303
2304 #[test]
2305 fn a_header_that_is_not_there_says_where_it_looked() {
2306 let mut run = Run::new();
2307 run.dir("/dir");
2308 run.go("#include <nope.h>\n");
2309 let diagnostics = run.pp.take_diagnostics();
2310 assert_eq!(diagnostics.len(), 1);
2311 assert_eq!(diagnostics[0].code, Some("E0341"));
2312 assert_eq!(diagnostics[0].message, "`nope.h` file not found");
2313 assert!(diagnostics[0].children[0].message.contains("/dir"));
2314 }
2315
2316 #[test]
2317 fn an_include_that_is_not_a_header_name_is_reported() {
2318 let mut run = Run::new();
2319 run.go("#include 3\n");
2320 let diagnostics = run.pp.take_diagnostics();
2321 assert_eq!(diagnostics[0].code, Some("E0343"));
2322 }
2323
2324 #[test]
2325 fn a_header_that_includes_itself_stops() {
2326 let mut run = Run::new();
2327 run.file("/dir/loop.h", "#include <loop.h>\n");
2328 run.dir("/dir");
2329 run.go("#include <loop.h>\n");
2330 let diagnostics = run.pp.take_diagnostics();
2331 assert_eq!(diagnostics.len(), 1, "one complaint, not one per level");
2332 assert_eq!(diagnostics[0].code, Some("E0342"));
2333 }
2334
2335 #[test]
2336 fn an_include_in_a_dead_branch_is_not_read() {
2337 let mut run = Run::new();
2338 assert_eq!(run.go("#if 0\n#include <nothing.h>\n#endif\nafter\n"), "after");
2339 assert!(run.messages().is_empty(), "a skipped include is not resolved");
2340 }
2341
2342 #[test]
2343 fn embed_writes_the_bytes_of_the_resource() {
2344 let mut run = Run::new();
2345 run.bytes("/logo.bin", &[0, 1, 127, 128, 255]);
2346 assert_eq!(run.go("#embed \"logo.bin\"\n"), "0, 1, 127, 128, 255");
2347 assert!(run.messages().is_empty());
2348 }
2349
2350 #[test]
2351 fn an_embed_is_a_valid_initializer_on_both_sides_of_empty() {
2352 let mut run = Run::new();
2357 run.bytes("/some.bin", &[7, 8]);
2358 run.bytes("/none.bin", &[]);
2359 let line = |name: &str| {
2360 format!("{{\n#embed \"{name}\" prefix(0xEF,) suffix(,0xFE) if_empty(0)\n}}\n")
2361 };
2362 assert_eq!(run.go(&line("some.bin")), "{ 0xEF,7, 8 ,0xFE }");
2363 assert_eq!(run.go_named("/other.c", &line("none.bin")), "{ 0 }");
2364 assert!(run.messages().is_empty());
2365 }
2366
2367 #[test]
2368 fn the_limit_and_the_offset_choose_a_window_of_the_resource() {
2369 let mut run = Run::new();
2370 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
2371 assert_eq!(run.go("#embed \"eight.bin\" limit(3)\n"), "1, 2, 3");
2372 assert_eq!(
2373 run.go_named("/b.c", "#embed \"eight.bin\" gnu::offset(4) limit(3)\n"),
2374 "5, 6, 7"
2375 );
2376 assert_eq!(run.go_named("/c.c", "#embed \"eight.bin\" limit(0) if_empty(9)\n"), "9");
2379 assert_eq!(run.go_named("/d.c", "#embed \"eight.bin\" gnu::offset(99)\n"), "");
2380 assert!(run.messages().is_empty());
2381 }
2382
2383 #[test]
2384 fn the_limit_is_a_constant_expression_and_not_just_a_number() {
2385 let mut run = Run::new();
2388 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
2389 assert_eq!(
2390 run.go("#define CHUNK 2\n#embed \"eight.bin\" limit(CHUNK * 2)\n"),
2391 "1, 2, 3, 4"
2392 );
2393 assert!(run.messages().is_empty());
2394 }
2395
2396 #[test]
2397 fn a_misspelled_embed_parameter_is_refused_rather_than_ignored() {
2398 let mut run = Run::new();
2401 run.bytes("/eight.bin", &[1, 2]);
2402 assert_eq!(run.go("#embed \"eight.bin\" limits(1)\n"), "");
2403 assert_eq!(run.messages(), vec!["unknown `#embed` parameter `limits`".to_owned()]);
2404 let mut vendor = Run::new();
2405 vendor.bytes("/eight.bin", &[1, 2]);
2406 assert_eq!(vendor.go("#embed \"eight.bin\" clang::offset(1)\n"), "");
2407 assert_eq!(
2408 vendor.messages(),
2409 vec!["unknown `#embed` parameter `clang::offset`".to_owned()]
2410 );
2411 }
2412
2413 #[test]
2414 fn a_missing_embed_resource_is_reported_as_a_resource() {
2415 let mut run = Run::new();
2416 run.go("#embed <nothing.bin>\n");
2417 assert_eq!(run.messages(), vec!["`nothing.bin` resource not found".to_owned()]);
2418 }
2419
2420 #[test]
2421 fn has_embed_tells_missing_from_present_from_empty() {
2422 let mut run = Run::new();
2426 run.bytes("/some.bin", &[1]);
2427 run.bytes("/none.bin", &[]);
2428 let src = "#if __has_embed(\"none.bin\") == __STDC_EMBED_EMPTY__\nempty\n#endif\n\
2429 #if __has_embed(\"some.bin\") == __STDC_EMBED_FOUND__\nfound\n#endif\n\
2430 #if __has_embed(\"gone.bin\") == __STDC_EMBED_NOT_FOUND__\ngone\n#endif\n";
2431 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
2432 assert_eq!(run.go(src), "empty found gone");
2433 assert!(run.messages().is_empty());
2434 }
2435
2436 #[test]
2437 fn has_embed_takes_the_limit_into_account() {
2438 let mut run = Run::new();
2441 run.bytes("/some.bin", &[1, 2, 3]);
2442 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
2443 let src = "#if __has_embed(\"some.bin\" limit(0)) == __STDC_EMBED_EMPTY__\nempty\n#endif\n";
2444 assert_eq!(run.go(src), "empty");
2445 assert!(run.messages().is_empty());
2446 }
2447
2448 #[test]
2449 fn a_directive_may_have_space_before_the_hash_and_after_it() {
2450 assert_eq!(clean(" # define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
2451 }
2452
2453 #[test]
2454 fn a_definition_survives_across_a_conditional() {
2455 assert_eq!(clean("#if 1\n#define F 7\n#endif\nF\n"), "7");
2456 }
2457
2458 #[test]
2459 fn an_empty_if_expression_is_reported() {
2460 let mut run = Run::new();
2461 run.go("#if\n#endif\n");
2462 assert_eq!(run.messages(), vec!["`#if` with no expression".to_owned()]);
2463 }
2464
2465 #[test]
2466 fn the_file_and_the_line_say_where_the_use_is() {
2467 let mut run = Run::new();
2468 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2469 assert_eq!(run.go("__FILE__ __LINE__\n__LINE__\n"), "\"/main.c\" 1 2");
2470 assert!(run.messages().is_empty());
2471 }
2472
2473 #[test]
2474 fn a_macro_that_mentions_the_line_answers_with_the_call() {
2475 let mut run = Run::new();
2476 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2477 run.file("/where.h", "#define WHERE __FILE__ __LINE__\n");
2478 assert_eq!(run.go("#include \"where.h\"\n\n\nWHERE\n"), "\"/main.c\" 4");
2482 assert!(run.messages().is_empty());
2483 }
2484
2485 #[test]
2486 fn the_file_name_is_the_file_without_the_directories() {
2487 let mut run = Run::new();
2488 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2489 assert_eq!(run.go_named("/deep/down/main.c", "__FILE_NAME__\n"), "\"main.c\"");
2490 }
2491
2492 #[test]
2493 fn a_backslash_in_the_name_is_escaped() {
2494 let mut run = Run::new();
2495 run.predefine("x86_64-pc-windows-msvc", &Predef::new());
2496 let text = run.go_named("C:\\src\\main.c", "__FILE__ __FILE_NAME__\n");
2499 assert_eq!(text, "\"C:\\\\src\\\\main.c\" \"main.c\"");
2500 }
2501
2502 #[test]
2503 fn the_base_file_is_the_one_named_on_the_command_line() {
2504 let mut run = Run::new();
2505 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2506 run.file("/deep.h", "__FILE__ __BASE_FILE__\n");
2507 assert_eq!(run.go("#include \"deep.h\"\n"), "\"/deep.h\" \"/main.c\"");
2508 assert!(run.messages().is_empty());
2509 }
2510
2511 #[test]
2512 fn the_include_level_counts_the_headers_above_it() {
2513 let mut run = Run::new();
2514 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2515 run.file("/one.h", "__INCLUDE_LEVEL__\n#include \"two.h\"\n");
2516 run.file("/two.h", "__INCLUDE_LEVEL__\n");
2517 assert_eq!(run.go("__INCLUDE_LEVEL__\n#include \"one.h\"\n"), "0 1 2");
2518 assert!(run.messages().is_empty());
2519 }
2520
2521 #[test]
2522 fn the_counter_is_a_different_number_every_time() {
2523 let mut run = Run::new();
2524 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2525 assert_eq!(run.go("__COUNTER__ __COUNTER__ __COUNTER__\n"), "0 1 2");
2526 }
2527
2528 #[test]
2529 fn the_counter_advances_once_per_argument_rather_than_once_per_use() {
2530 let mut run = Run::new();
2531 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2532 assert_eq!(run.go("#define TWICE(x) x x\nTWICE(__COUNTER__) __COUNTER__\n"), "0 0 1");
2536 }
2537
2538 #[test]
2539 fn the_line_is_a_number_an_if_can_use() {
2540 let mut run = Run::new();
2541 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2542 assert_eq!(run.go("#if __LINE__ == 1 && __INCLUDE_LEVEL__ == 0\nyes\n#endif\n"), "yes");
2543 assert!(run.messages().is_empty());
2544 }
2545
2546 #[test]
2547 fn the_dynamic_macros_are_defined_like_any_others() {
2548 let mut run = Run::new();
2549 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2550 let src = "#ifdef __FILE__\nyes\n#endif\n#undef __LINE__\n#ifndef __LINE__\ngone\n#endif\n";
2551 assert_eq!(run.go(src), "yes gone");
2552 assert!(run.messages().is_empty(), "`#undef` of a builtin is allowed, as it is in GCC");
2553 }
2554
2555 #[test]
2556 fn redefining_a_dynamic_macro_warns_and_points_at_the_built_in_file() {
2557 let mut run = Run::new();
2558 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2559 assert_eq!(run.go("#define __FILE__ \"mine.c\"\n__FILE__\n"), "\"mine.c\"");
2560 let complaints = run.pp.take_diagnostics();
2561 assert_eq!(complaints.len(), 1);
2562 assert_eq!(complaints[0].code, Some("W0301"));
2563 let previous = complaints[0].children.first().expect("a note saying where it was");
2564 assert_eq!(run.sources.lookup(previous.span.lo).map(|loc| loc.file), {
2565 let built_in = run.sources.files().iter().find(|f| f.name == BUILT_IN);
2566 built_in.map(|f| f.id)
2567 });
2568 }
2569
2570 #[test]
2571 fn destringizing_undoes_what_stringizing_did() {
2572 assert_eq!(destringize(r#""a \"b\" c""#), r#"a "b" c"#);
2573 assert_eq!(destringize(r#""a \\ b""#), r"a \ b");
2574 assert_eq!(destringize(r#"L"wide""#), "wide");
2575 }
2576}