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 self.pragma_operator(expanded, out, cx.interner, names);
334 }
335
336 fn directive(
338 &mut self,
339 body: &[PpToken],
340 hash: Span,
341 out: &mut Vec<Tok>,
342 cx: &mut Context<'_>,
343 names: &Names,
344 ) {
345 let Some(first) = body.first().copied() else {
346 return;
347 };
348 let name = ident_of(&first);
349 let rest = &body[1..];
350
351 if name == Some(names.r#if) {
354 let value = self.live() && self.eval(rest, hash, cx, names);
355 self.open(hash, value);
356 return;
357 }
358 if name == Some(names.ifdef) || name == Some(names.ifndef) {
359 let want = name == Some(names.ifdef);
360 let value = self.live() && self.defined_check(rest, hash, want, names);
361 self.open(hash, value);
362 return;
363 }
364 if name == Some(names.elif) || name == Some(names.elifdef) || name == Some(names.elifndef) {
365 self.elif(name, rest, hash, cx, names);
366 return;
367 }
368 if name == Some(names.r#else) {
369 self.branch_else(rest, hash);
370 return;
371 }
372 if name == Some(names.endif) {
373 self.endif(rest, hash);
374 return;
375 }
376 if !self.live() {
377 return;
381 }
382
383 let interner = &mut *cx.interner;
384 if name == Some(names.define) {
385 let (def, diagnostics) = parse_define(rest, interner);
386 self.diagnostics.extend(diagnostics);
387 if let Some(def) = def {
388 if let Some(problem) = self.macros.define(def, interner) {
389 self.diagnostics.push(problem);
390 }
391 }
392 } else if name == Some(names.undef) {
393 self.undef(rest, hash, interner);
394 } else if name == Some(names.error) || name == Some(names.warning) {
395 self.message(rest, hash, name == Some(names.error), interner);
396 } else if name == Some(names.line) {
397 self.line(rest, hash, cx);
398 } else if name == Some(names.pragma) {
399 if rest.len() == 1 && ident_of(&rest[0]) == Some(names.once) {
406 self.pragma_once(hash);
407 } else {
408 self.pass_through(body, hash, out);
409 }
410 } else if name == Some(names.include) || name == Some(names.include_next) {
411 self.include(rest, hash, name == Some(names.include_next), out, cx, names);
412 } else if name == Some(names.embed) {
413 self.embed(rest, hash, out, cx);
414 } else {
415 self.diagnostics.push(
416 Diagnostic::error("invalid preprocessing directive", first.span).with_code("E0332"),
417 );
418 }
419 }
420
421 fn pragma_once(&mut self, hash: Span) {
423 if self.stack.len() <= 1 {
426 self.diagnostics.push(
427 Diagnostic::warning("`#pragma once` in the main file", hash).with_code("W0332"),
428 );
429 return;
430 }
431 if let Some(frame) = self.stack.last() {
432 self.seen.insert(frame.path.clone(), Guard::Once);
433 }
434 }
435
436 fn skip(&self, path: &Path) -> bool {
438 match self.seen.get(path) {
439 Some(Guard::Once) => true,
440 Some(Guard::Macro(name)) => self.macros.is_defined(*name),
441 None => false,
442 }
443 }
444
445 fn pass_through(&mut self, body: &[PpToken], hash: Span, out: &mut Vec<Tok>) {
447 let _ = self;
448 out.push(Tok::synthetic(
449 PpTokenKind::Punct(Punct::Hash),
450 None,
451 TokenFlags::START_OF_LINE,
452 hash,
453 ));
454 out.extend(body.iter().copied().map(Tok::new));
455 }
456
457 fn include(
459 &mut self,
460 rest: &[PpToken],
461 hash: Span,
462 is_next: bool,
463 out: &mut Vec<Tok>,
464 cx: &mut Context<'_>,
465 names: &Names,
466 ) {
467 let Some(header) = self.header_of(rest, hash, cx) else {
468 return;
469 };
470 let (form, relative_to, from) = self.where_to_look(&header, is_next, cx);
471 let found = cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from);
472 let Some(found) = found else {
473 let tried = cx.search.tried(&header.name, form, relative_to.as_deref(), from);
474 let where_looked = if tried.is_empty() {
475 "the name is an absolute path, so the search path was not used".to_owned()
476 } else {
477 let list: Vec<String> =
478 tried.iter().map(|d| d.to_string_lossy().into_owned()).collect();
479 format!("searched: {}", list.join(", "))
480 };
481 self.diagnostics.push(
482 Diagnostic::error(format!("`{}` file not found", header.name), hash)
483 .with_code("E0341")
484 .note(where_looked, hash),
485 );
486 return;
487 };
488 if self.skip(&found.path) {
493 return;
494 }
495 if self.stack.len() >= cx.max_include_depth as usize {
496 let mut diagnostic =
497 Diagnostic::error("`#include` nested too deeply", hash).with_code("E0342").note(
498 "a header that includes itself with no include guard is the usual cause",
499 hash,
500 );
501 if let Some(outer) = self.stack.first().filter(|f| !f.at.is_dummy()) {
502 diagnostic = diagnostic.note("the outermost include is here", outer.at);
503 }
504 self.diagnostics.push(diagnostic);
505 return;
506 }
507 let added = cx.sources.add_shared(found.name.clone(), found.bytes.clone(), Some(hash));
508 let file = match added {
509 Ok(file) => file,
510 Err(full) => {
511 self.diagnostics.push(Diagnostic::error(full.to_string(), hash).with_code("E0344"));
512 return;
513 }
514 };
515 self.stack.push(Frame {
516 at: hash,
517 dir: found.path.parent().map(Path::to_path_buf),
518 path: found.path,
519 next: found.next,
520 });
521 self.process(file, out, cx, names);
522 self.stack.pop();
523 }
524
525 fn embed(&mut self, rest: &[PpToken], hash: Span, out: &mut Vec<Tok>, cx: &mut Context<'_>) {
527 let Some((header, params)) = self.embed_line(rest, hash, cx) else {
528 return;
529 };
530 let Some(found) = self.find(&header, false, cx) else {
531 self.diagnostics.push(
532 Diagnostic::error(format!("`{}` resource not found", header.name), hash)
533 .with_code("E0341")
534 .note("an `#embed` resource is looked for on the include path", hash),
535 );
536 return;
537 };
538 embed::tokens(found.bytes.as_slice(), ¶ms, hash, cx.interner, out);
543 }
544
545 fn embed_line(
547 &mut self,
548 rest: &[PpToken],
549 hash: Span,
550 cx: &mut Context<'_>,
551 ) -> Option<(Header, embed::Params)> {
552 if rest.is_empty() {
553 self.bad_header(hash);
554 return None;
555 }
556 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
557 let line = if line[0].kind == PpTokenKind::HeaderName {
563 line
564 } else {
565 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
566 self.diagnostics.append(&mut self.expander.take_diagnostics());
567 expanded
568 };
569 let Some(used) = embed::header_length(&line) else {
570 self.bad_header(line.first().map_or(hash, |t| t.report_span()));
571 return None;
572 };
573 let header = if line[0].kind == PpTokenKind::HeaderName {
574 header_from_token(spelling(line[0], cx.interner))
575 } else {
576 let spellings: Vec<&str> =
577 line[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
578 header_from_tokens(&spellings)
579 };
580 let Some(header) = header else {
581 self.bad_header(line[0].report_span());
582 return None;
583 };
584 let params = self.embed_params(&line[used..], hash, cx)?;
585 Some((header, params))
586 }
587
588 fn embed_params(
590 &mut self,
591 line: &[Tok],
592 at: Span,
593 cx: &mut Context<'_>,
594 ) -> Option<embed::Params> {
595 let Preprocessor { expander, macros, diagnostics, .. } = self;
596 let sources = &mut *cx.sources;
597 let mut expand = |toks: Vec<Tok>, interner: &mut Interner| {
598 expander.expand_toks(toks, macros, interner, sources)
599 };
600 let params = embed::parse(line, at, cx.interner, diagnostics, &mut expand);
601 self.diagnostics.append(&mut self.expander.take_diagnostics());
602 params
603 }
604
605 fn where_to_look(
616 &self,
617 header: &Header,
618 is_next: bool,
619 cx: &Context<'_>,
620 ) -> (IncludeForm, Option<PathBuf>, usize) {
621 let form = if header.angled { IncludeForm::Angled } else { IncludeForm::Quoted };
622 let frame = self.stack.last();
623 let from = if is_next {
624 frame.map_or(0, |f| f.next).max(cx.search.start(form))
625 } else {
626 cx.search.start(form)
627 };
628 let relative_to = if is_next { None } else { frame.and_then(|f| f.dir.clone()) };
629 (form, relative_to, from)
630 }
631
632 fn find(&self, header: &Header, is_next: bool, cx: &Context<'_>) -> Option<Found> {
634 let (form, relative_to, from) = self.where_to_look(header, is_next, cx);
635 cx.search.resolve(cx.fs, &header.name, form, relative_to.as_deref(), from)
636 }
637
638 fn header_of(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>) -> Option<Header> {
640 if let Some(first) = rest.first().copied() {
641 if first.kind == PpTokenKind::HeaderName {
642 let text = first.value.map_or("", |v| cx.interner.resolve(v));
643 let header = header_from_token(text);
644 if header.is_none() {
645 self.bad_header(first.span);
646 }
647 self.extra_tokens(&rest[1..], "#include");
648 return header;
649 }
650 }
651 if rest.is_empty() {
655 self.bad_header(hash);
656 return None;
657 }
658 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
659 let expanded = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
660 self.diagnostics.append(&mut self.expander.take_diagnostics());
661 let spellings: Vec<&str> = expanded.iter().map(|t| spelling(*t, cx.interner)).collect();
662 let header = header_from_tokens(&spellings);
663 if header.is_none() {
664 let at = expanded.first().map_or(hash, |t| t.report_span());
665 self.bad_header(at);
666 }
667 header
668 }
669
670 fn bad_operand(&mut self, tok: Tok, at: Span, interner: &Interner) {
672 self.diagnostics.push(
673 Diagnostic::error(
674 format!("expected an identifier as the operand of `{}`", spelling(tok, interner)),
675 at,
676 )
677 .with_code("E0345"),
678 );
679 }
680
681 fn bad_header(&mut self, at: Span) {
682 self.diagnostics.push(
683 Diagnostic::error("expected a file name in `<>` or `\"\"`", at).with_code("E0343"),
684 );
685 }
686
687 fn open(&mut self, span: Span, value: bool) {
689 let enclosing_live = self.live();
690 self.conds.push(Cond {
691 span,
692 live: enclosing_live && value,
693 taken: value,
694 enclosing_live,
695 seen_else: false,
696 });
697 }
698
699 fn elif(
700 &mut self,
701 name: Option<Symbol>,
702 rest: &[PpToken],
703 hash: Span,
704 cx: &mut Context<'_>,
705 names: &Names,
706 ) {
707 let Some(top) = self.conds.last() else {
708 self.stray("elif", hash);
709 return;
710 };
711 if top.seen_else {
712 self.diagnostics
713 .push(Diagnostic::error("`#elif` after `#else`", hash).with_code("E0333"));
714 return;
715 }
716 let (enclosing_live, already_taken) = (top.enclosing_live, top.taken);
719 let consider = enclosing_live && !already_taken;
720 let value = if !consider {
721 false
722 } else if name == Some(names.elif) {
723 self.eval(rest, hash, cx, names)
724 } else {
725 self.defined_check(rest, hash, name == Some(names.elifdef), names)
726 };
727 let top = self.conds.last_mut().expect("checked above and nothing popped");
728 top.live = consider && value;
729 top.taken = already_taken || value;
730 }
731
732 fn branch_else(&mut self, rest: &[PpToken], hash: Span) {
733 let Some(top) = self.conds.last_mut() else {
734 self.stray("else", hash);
735 return;
736 };
737 if top.seen_else {
738 self.diagnostics.push(Diagnostic::error("a second `#else`", hash).with_code("E0333"));
739 return;
740 }
741 top.live = top.enclosing_live && !top.taken;
742 top.taken = true;
743 top.seen_else = true;
744 let enclosing_live = top.enclosing_live;
745 if enclosing_live {
746 self.extra_tokens(rest, "#else");
747 }
748 }
749
750 fn endif(&mut self, rest: &[PpToken], hash: Span) {
751 if self.conds.pop().is_none() {
752 self.stray("endif", hash);
753 return;
754 }
755 if self.live() {
756 self.extra_tokens(rest, "#endif");
757 }
758 }
759
760 fn stray(&mut self, what: &str, hash: Span) {
761 self.diagnostics
762 .push(Diagnostic::error(format!("`#{what}` without `#if`"), hash).with_code("E0334"));
763 }
764
765 fn extra_tokens(&mut self, rest: &[PpToken], what: &str) {
770 if let Some(first) = rest.first() {
771 self.diagnostics.push(
772 Diagnostic::warning(format!("extra tokens after `{what}`"), first.span)
773 .with_code("W0330"),
774 );
775 }
776 }
777
778 fn eval(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>, names: &Names) -> bool {
780 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
781 let line = self.resolve_defined(line, cx.interner, names);
787 let line = self.resolve_has(line, cx, names, true);
792 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
793 self.diagnostics.append(&mut self.expander.take_diagnostics());
794 let line = self.resolve_defined(line, cx.interner, names);
795 let line = self.resolve_has(line, cx, names, false);
796 cond::evaluate(&line, cx.interner, &mut self.diagnostics, hash)
797 }
798
799 fn resolve_has(
804 &mut self,
805 line: Vec<Tok>,
806 cx: &mut Context<'_>,
807 names: &Names,
808 headers_only: bool,
809 ) -> Vec<Tok> {
810 if !line.iter().any(|t| t.ident().is_some_and(|n| names.has.op(n).is_some())) {
811 return line;
812 }
813 let mut out = Vec::with_capacity(line.len());
814 let mut at = 0;
815 while at < line.len() {
816 let tok = line[at];
817 let op = tok.ident().and_then(|n| names.has.op(n));
818 let Some(op) = op.filter(|op| !headers_only || op.is_header()) else {
819 out.push(tok);
820 at += 1;
821 continue;
822 };
823 let Some((operand, after)) = arguments(&line, at + 1) else {
824 if !headers_only {
828 self.diagnostics.push(
829 Diagnostic::error(
830 format!("expected `(` after `{}`", spelling(tok, cx.interner)),
831 tok.report_span(),
832 )
833 .with_code("E0345"),
834 );
835 }
836 out.push(tok);
837 at += 1;
838 continue;
839 };
840 at = after;
841 let value = self.ask(op, operand, tok, cx);
844 let sym = cx.interner.intern(&value.to_string());
845 out.push(Tok::synthetic(PpTokenKind::Number, Some(sym), tok.flags, tok.report_span()));
846 }
847 out
848 }
849
850 fn ask(&mut self, op: Op, operand: &[Tok], tok: Tok, cx: &mut Context<'_>) -> u32 {
852 let at = operand.first().map_or(tok.report_span(), |t| t.report_span());
853 match op {
854 Op::Include | Op::IncludeNext => {
855 let spellings: Vec<&str> =
856 operand.iter().map(|t| spelling(*t, cx.interner)).collect();
857 let Some(header) = header_from_tokens(&spellings) else {
858 self.bad_header(at);
859 return 0;
860 };
861 u32::from(self.find(&header, op == Op::IncludeNext, cx).is_some())
862 }
863 Op::Embed => {
864 let Some(used) = embed::header_length(operand) else {
869 self.bad_header(at);
870 return 0;
871 };
872 let header = if operand[0].kind == PpTokenKind::HeaderName {
873 header_from_token(spelling(operand[0], cx.interner))
874 } else {
875 let spellings: Vec<&str> =
876 operand[..used].iter().map(|t| spelling(*t, cx.interner)).collect();
877 header_from_tokens(&spellings)
878 };
879 let Some(header) = header else {
880 self.bad_header(at);
881 return 0;
882 };
883 let Some(params) = self.embed_params(&operand[used..], at, cx) else {
888 return 0;
889 };
890 match self.find(&header, false, cx) {
891 None => 0,
892 Some(found) => {
893 let taken = params.taken(found.bytes.as_slice().len() as u64);
894 if taken == 0 { 2 } else { 1 }
895 }
896 }
897 }
898 Op::BuildingModule => {
899 if attribute_name(operand, cx.interner).is_none() {
900 self.bad_operand(tok, at, cx.interner);
901 }
902 0
909 }
910 Op::Table(kind) => {
911 let Some(name) = attribute_name(operand, cx.interner) else {
912 self.bad_operand(tok, at, cx.interner);
913 return 0;
914 };
915 match kind {
916 Kind::Attribute => rucc_gnu::has_attribute(name),
917 Kind::CAttribute => rucc_gnu::has_c_attribute(name),
918 Kind::Builtin => rucc_gnu::has_builtin(name),
919 Kind::Feature => rucc_gnu::has_feature(name),
920 Kind::Extension => rucc_gnu::has_extension(name),
921 }
922 }
923 }
924 }
925
926 fn resolve_defined(
928 &mut self,
929 line: Vec<Tok>,
930 interner: &mut Interner,
931 names: &Names,
932 ) -> Vec<Tok> {
933 if !line.iter().any(|t| t.ident() == Some(names.defined)) {
934 return line;
935 }
936 let mut out = Vec::with_capacity(line.len());
937 let mut at = 0;
938 while at < line.len() {
939 let tok = line[at];
940 if tok.ident() != Some(names.defined) {
941 out.push(tok);
942 at += 1;
943 continue;
944 }
945 let parenthesised = line.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
946 let name_at = if parenthesised { at + 2 } else { at + 1 };
947 let name = line.get(name_at).and_then(|t| t.ident());
948 let Some(name) = name else {
949 self.diagnostics.push(
950 Diagnostic::error("`defined` without a macro name", tok.report_span())
951 .with_code("E0335"),
952 );
953 out.push(tok);
954 at += 1;
955 continue;
956 };
957 at = name_at + 1;
958 if parenthesised {
959 if line.get(at).is_some_and(|t| t.is(Punct::RParen)) {
960 at += 1;
961 } else {
962 self.diagnostics.push(
963 Diagnostic::error("expected `)` after `defined`", tok.report_span())
964 .with_code("E0335"),
965 );
966 }
967 }
968 let value = self.macros.is_defined(name) || names.has.op(name).is_some();
972 out.push(number(value, tok.flags, tok.report_span(), interner));
973 }
974 out
975 }
976
977 fn defined_check(
979 &mut self,
980 rest: &[PpToken],
981 hash: Span,
982 want_defined: bool,
983 names: &Names,
984 ) -> bool {
985 let Some(name) = rest.first().and_then(ident_of) else {
986 self.diagnostics.push(
987 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
988 .with_code("E0336"),
989 );
990 return false;
991 };
992 self.extra_tokens(&rest[1..], if want_defined { "#ifdef" } else { "#ifndef" });
993 let defined = self.macros.is_defined(name) || names.has.op(name).is_some();
994 defined == want_defined
995 }
996
997 fn undef(&mut self, rest: &[PpToken], hash: Span, interner: &Interner) {
998 let Some(name) = rest.first().and_then(ident_of) else {
999 self.diagnostics.push(
1000 Diagnostic::error("expected a macro name", rest.first().map_or(hash, |t| t.span))
1001 .with_code("E0336"),
1002 );
1003 return;
1004 };
1005 let text = interner.resolve(name);
1008 if text == "defined" || text.starts_with("__STDC_") {
1009 self.diagnostics.push(
1010 Diagnostic::error(format!("`{text}` cannot be undefined"), rest[0].span)
1011 .with_code("E0337"),
1012 );
1013 return;
1014 }
1015 self.macros.undef(name);
1016 self.extra_tokens(&rest[1..], "#undef");
1017 }
1018
1019 fn message(&mut self, rest: &[PpToken], hash: Span, fatal: bool, interner: &Interner) {
1021 let text = spell_line(rest, interner);
1022 let span = rest.first().map_or(hash, |t| t.span.to(last_span(rest)));
1023 let diag = if fatal {
1024 Diagnostic::error(text, span).with_code("E0338")
1025 } else {
1026 Diagnostic::warning(text, span).with_code("W0331")
1027 };
1028 self.diagnostics.push(diag);
1029 }
1030
1031 fn line(&mut self, rest: &[PpToken], hash: Span, cx: &mut Context<'_>) {
1036 let line: Vec<Tok> = rest.iter().copied().map(Tok::new).collect();
1037 let line = self.expander.expand_toks(line, &self.macros, cx.interner, cx.sources);
1038 self.diagnostics.append(&mut self.expander.take_diagnostics());
1039 let interner = &mut *cx.interner;
1040
1041 let number_text = line
1042 .first()
1043 .filter(|t| t.kind == PpTokenKind::Number)
1044 .and_then(|t| t.value)
1045 .map(|v| interner.resolve(v));
1046 let Some(parsed) = number_text.and_then(|t| t.parse::<u64>().ok()) else {
1047 self.diagnostics.push(
1048 Diagnostic::error(
1049 "`#line` needs a decimal line number",
1050 line.first().map_or(hash, |t| t.report_span()),
1051 )
1052 .with_code("E0339"),
1053 );
1054 return;
1055 };
1056 if parsed == 0 || parsed > 2_147_483_647 {
1059 self.diagnostics.push(
1060 Diagnostic::error("`#line` number is out of range", line[0].report_span())
1061 .with_code("E0339"),
1062 );
1063 return;
1064 }
1065
1066 let mut file = None;
1067 if let Some(second) = line.get(1) {
1068 if second.kind == PpTokenKind::StringLit {
1069 file = second.value;
1070 } else {
1071 self.diagnostics.push(
1072 Diagnostic::error(
1073 "`#line` file name must be a string literal",
1074 second.report_span(),
1075 )
1076 .with_code("E0339"),
1077 );
1078 return;
1079 }
1080 }
1081 #[expect(
1082 clippy::cast_possible_truncation,
1083 reason = "the range check above keeps this inside i32, let alone u32"
1084 )]
1085 self.lines.push(LineDirective { span: hash, line: parsed as u32, file });
1086 }
1087
1088 fn pragma_operator(
1094 &mut self,
1095 expanded: Vec<Tok>,
1096 out: &mut Vec<Tok>,
1097 interner: &mut Interner,
1098 names: &Names,
1099 ) {
1100 if !expanded.iter().any(|t| t.ident() == Some(names.pragma_op)) {
1101 out.extend(expanded);
1102 return;
1103 }
1104 let mut at = 0;
1105 while at < expanded.len() {
1106 let tok = expanded[at];
1107 if tok.ident() != Some(names.pragma_op) {
1108 out.push(tok);
1109 at += 1;
1110 continue;
1111 }
1112 let open = expanded.get(at + 1).is_some_and(|t| t.is(Punct::LParen));
1113 let text = expanded.get(at + 2).filter(|t| t.kind == PpTokenKind::StringLit);
1114 let close = expanded.get(at + 3).is_some_and(|t| t.is(Punct::RParen));
1115 let (Some(text), true, true) = (text, open, close) else {
1116 self.diagnostics.push(
1117 Diagnostic::error("`_Pragma` takes a single string literal", tok.report_span())
1118 .with_code("E0340"),
1119 );
1120 out.push(tok);
1121 at += 1;
1122 continue;
1123 };
1124 let literal = text.value.map(|v| interner.resolve(v)).unwrap_or_default();
1125 let body = destringize(literal);
1126 self.emit_pragma(&body, tok, out, interner, names);
1127 at += 4;
1128 }
1129 }
1130
1131 fn emit_pragma(
1133 &mut self,
1134 body: &str,
1135 at: Tok,
1136 out: &mut Vec<Tok>,
1137 interner: &mut Interner,
1138 names: &Names,
1139 ) {
1140 let span = at.report_span();
1141 let (tokens, diagnostics) = tokenize(body.as_bytes(), 0, Options::new(), interner);
1142 self.diagnostics.extend(
1145 diagnostics
1146 .into_iter()
1147 .map(|d| Diagnostic::new(d.severity, d.message, span).with_code("E0340")),
1148 );
1149 out.push(Tok::synthetic(
1150 PpTokenKind::Punct(Punct::Hash),
1151 None,
1152 TokenFlags::START_OF_LINE,
1153 span,
1154 ));
1155 out.push(Tok::synthetic(PpTokenKind::Ident, Some(names.pragma), TokenFlags::EMPTY, span));
1156 for (at, t) in tokens.into_iter().filter(|t| !t.is_eof()).enumerate() {
1160 let spaced = at == 0 || t.flags.has(TokenFlags::LEADING_SPACE);
1163 let flags = if spaced {
1164 TokenFlags::EMPTY.with(TokenFlags::LEADING_SPACE)
1165 } else {
1166 TokenFlags::EMPTY
1167 };
1168 out.push(Tok::synthetic(t.kind, t.value, flags, span));
1169 }
1170 }
1171}
1172
1173fn guard_opener(body: &[PpToken], names: &Names) -> Option<Symbol> {
1178 let name = ident_of(body.first()?)?;
1179 let rest = &body[1..];
1180 if name == names.ifndef {
1181 let [only] = rest else {
1182 return None;
1183 };
1184 return ident_of(only);
1185 }
1186 if name != names.r#if {
1187 return None;
1188 }
1189 let [bang, defined, tail @ ..] = rest else {
1190 return None;
1191 };
1192 if bang.punct() != Some(Punct::Bang) || ident_of(defined) != Some(names.defined) {
1193 return None;
1194 }
1195 match tail {
1196 [only] => ident_of(only),
1197 [open, only, close]
1198 if open.punct() == Some(Punct::LParen) && close.punct() == Some(Punct::RParen) =>
1199 {
1200 ident_of(only)
1201 }
1202 _ => None,
1203 }
1204}
1205
1206fn is_include(name: Option<Symbol>, names: &Names) -> bool {
1208 name == Some(names.include) || name == Some(names.include_next) || name == Some(names.embed)
1209}
1210
1211fn is_directive(tok: PpToken) -> bool {
1213 tok.flags.has(TokenFlags::START_OF_LINE) && tok.punct() == Some(Punct::Hash)
1214}
1215
1216fn ident_of(tok: &PpToken) -> Option<Symbol> {
1217 match tok.kind {
1218 PpTokenKind::Ident => tok.value,
1219 _ => None,
1220 }
1221}
1222
1223fn last_span(tokens: &[PpToken]) -> Span {
1224 tokens.last().map_or(Span::DUMMY, |t| t.span)
1225}
1226
1227fn number(value: bool, flags: TokenFlags, span: Span, interner: &mut Interner) -> Tok {
1229 let sym = interner.intern(if value { "1" } else { "0" });
1230 Tok::synthetic(PpTokenKind::Number, Some(sym), flags, span)
1231}
1232
1233fn spell_line(tokens: &[PpToken], interner: &Interner) -> String {
1235 let mut out = String::new();
1236 for (index, tok) in tokens.iter().enumerate() {
1237 if index > 0 && tok.flags.has(TokenFlags::LEADING_SPACE) {
1238 out.push(' ');
1239 }
1240 match tok.value {
1241 Some(sym) => out.push_str(interner.resolve(sym)),
1242 None => {
1243 if let Some(p) = tok.punct() {
1244 out.push_str(p.as_str());
1245 }
1246 }
1247 }
1248 }
1249 out
1250}
1251
1252fn destringize(literal: &str) -> String {
1257 let body = literal
1258 .trim_start_matches(['L', 'u', 'U', '8'])
1259 .strip_prefix('"')
1260 .and_then(|s| s.strip_suffix('"'))
1261 .unwrap_or(literal);
1262 let mut out = String::with_capacity(body.len());
1263 let mut chars = body.chars();
1264 while let Some(c) = chars.next() {
1265 if c != '\\' {
1266 out.push(c);
1267 continue;
1268 }
1269 match chars.next() {
1270 Some('"') => out.push('"'),
1271 Some('\\') => out.push('\\'),
1272 Some(other) => {
1273 out.push('\\');
1274 out.push(other);
1275 }
1276 None => out.push('\\'),
1277 }
1278 }
1279 out
1280}
1281
1282fn arguments(line: &[Tok], at: usize) -> Option<(&[Tok], usize)> {
1288 if !line.get(at)?.is(Punct::LParen) {
1289 return None;
1290 }
1291 let mut depth = 1u32;
1292 let mut end = at + 1;
1293 while end < line.len() {
1294 if line[end].is(Punct::LParen) {
1295 depth += 1;
1296 } else if line[end].is(Punct::RParen) {
1297 depth -= 1;
1298 if depth == 0 {
1299 return Some((&line[at + 1..end], end + 1));
1300 }
1301 }
1302 end += 1;
1303 }
1304 None
1305}
1306
1307fn attribute_name<'i>(operand: &[Tok], interner: &'i Interner) -> Option<&'i str> {
1313 let name = match operand {
1314 [one] => one,
1315 [_, scope, name] if scope.is(Punct::ColonColon) => name,
1316 _ => return None,
1317 };
1318 name.ident().map(|sym| interner.resolve(sym))
1319}
1320
1321#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1323enum Op {
1324 Include,
1326 IncludeNext,
1328 Embed,
1331 BuildingModule,
1333 Table(Kind),
1335}
1336
1337impl Op {
1338 fn is_header(self) -> bool {
1340 matches!(self, Op::Include | Op::IncludeNext | Op::Embed)
1341 }
1342}
1343
1344struct HasOps {
1349 ops: [(Symbol, Op); 9],
1350}
1351
1352impl HasOps {
1353 fn new(interner: &mut Interner) -> HasOps {
1354 HasOps {
1355 ops: [
1356 (interner.intern("__has_include"), Op::Include),
1357 (interner.intern("__has_include_next"), Op::IncludeNext),
1358 (interner.intern("__has_embed"), Op::Embed),
1359 (interner.intern("__has_attribute"), Op::Table(Kind::Attribute)),
1360 (interner.intern("__has_c_attribute"), Op::Table(Kind::CAttribute)),
1361 (interner.intern("__has_builtin"), Op::Table(Kind::Builtin)),
1362 (interner.intern("__has_feature"), Op::Table(Kind::Feature)),
1363 (interner.intern("__has_extension"), Op::Table(Kind::Extension)),
1364 (interner.intern("__building_module"), Op::BuildingModule),
1365 ],
1366 }
1367 }
1368
1369 fn op(&self, name: Symbol) -> Option<Op> {
1371 self.ops.iter().find(|(sym, _)| *sym == name).map(|(_, op)| *op)
1372 }
1373}
1374
1375struct Names {
1381 define: Symbol,
1382 undef: Symbol,
1383 r#if: Symbol,
1384 ifdef: Symbol,
1385 ifndef: Symbol,
1386 elif: Symbol,
1387 elifdef: Symbol,
1388 elifndef: Symbol,
1389 r#else: Symbol,
1390 endif: Symbol,
1391 line: Symbol,
1392 error: Symbol,
1393 warning: Symbol,
1394 pragma: Symbol,
1395 include: Symbol,
1396 include_next: Symbol,
1397 embed: Symbol,
1398 defined: Symbol,
1399 once: Symbol,
1400 pragma_op: Symbol,
1401 has: HasOps,
1402}
1403
1404impl Names {
1405 fn new(interner: &mut Interner) -> Names {
1406 Names {
1407 define: interner.intern("define"),
1408 undef: interner.intern("undef"),
1409 r#if: interner.intern("if"),
1410 ifdef: interner.intern("ifdef"),
1411 ifndef: interner.intern("ifndef"),
1412 elif: interner.intern("elif"),
1413 elifdef: interner.intern("elifdef"),
1414 elifndef: interner.intern("elifndef"),
1415 r#else: interner.intern("else"),
1416 endif: interner.intern("endif"),
1417 line: interner.intern("line"),
1418 error: interner.intern("error"),
1419 warning: interner.intern("warning"),
1420 pragma: interner.intern("pragma"),
1421 include: interner.intern("include"),
1422 include_next: interner.intern("include_next"),
1423 embed: interner.intern("embed"),
1424 defined: interner.intern("defined"),
1425 once: interner.intern("once"),
1426 pragma_op: interner.intern("_Pragma"),
1427 has: HasOps::new(interner),
1428 }
1429 }
1430}
1431
1432#[cfg(test)]
1433mod tests {
1434 use rucc_diag::{Severity, SourceMap};
1435 use rucc_session::{MemoryFileSystem, SearchPath};
1436
1437 use super::*;
1438 use rucc_session::Std;
1439
1440 use crate::predef::Timestamp;
1441
1442 struct Run {
1447 interner: Interner,
1448 sources: SourceMap,
1449 fs: MemoryFileSystem,
1450 search: SearchPath,
1451 pp: Preprocessor,
1452 }
1453
1454 impl Run {
1455 fn new() -> Run {
1456 Run {
1457 interner: Interner::new(),
1458 sources: SourceMap::new(),
1459 fs: MemoryFileSystem::new(),
1460 search: SearchPath::new(),
1461 pp: Preprocessor::new(),
1462 }
1463 }
1464
1465 fn file(&mut self, path: &str, contents: &str) {
1467 self.fs.insert(path, contents.as_bytes().to_vec());
1468 }
1469
1470 fn bytes(&mut self, path: &str, contents: &[u8]) {
1473 self.fs.insert(path, contents.to_vec());
1474 }
1475
1476 fn dir(&mut self, path: &str) {
1478 self.search.push_bracket(path);
1479 }
1480
1481 fn predefine(&mut self, triple: &str, opts: &Predef) {
1483 let target = TargetInfo::new(triple.parse().expect("a supported triple"));
1484 let mut cx =
1485 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1486 self.pp.predefine(&target, opts, &mut cx).expect("the map has room");
1487 }
1488
1489 fn go(&mut self, src: &str) -> String {
1491 self.go_named("/main.c", src)
1492 }
1493
1494 fn go_named(&mut self, path: &str, src: &str) -> String {
1496 let file = self.sources.add(path, src.as_bytes().to_vec()).expect("the map has room");
1497 let out = {
1498 let mut cx =
1499 Context::new(&mut self.interner, &mut self.sources, &self.fs, &self.search);
1500 self.pp.run(file, &mut cx)
1501 };
1502 let mut text = String::new();
1503 for (at, tok) in out.iter().enumerate() {
1504 let spaced = tok.flags.has(TokenFlags::LEADING_SPACE)
1505 || tok.flags.has(TokenFlags::START_OF_LINE);
1506 if at > 0 && spaced {
1507 text.push(' ');
1508 }
1509 match tok.kind {
1510 PpTokenKind::Punct(p) => text.push_str(p.as_str()),
1511 _ => text.push_str(
1512 self.interner.resolve(tok.value.expect("every non-punctuator interns")),
1513 ),
1514 }
1515 }
1516 text
1517 }
1518
1519 fn files(&self) -> usize {
1523 self.sources.files().len()
1524 }
1525
1526 fn messages(&mut self) -> Vec<String> {
1527 self.pp.take_diagnostics().into_iter().map(|d| d.message).collect()
1528 }
1529
1530 fn severities(&mut self) -> Vec<Severity> {
1531 self.pp.diagnostics().iter().map(|d| d.severity).collect()
1532 }
1533 }
1534
1535 fn clean(src: &str) -> String {
1536 let mut run = Run::new();
1537 let text = run.go(src);
1538 assert!(run.messages().is_empty(), "expected no diagnostics from {src:?}");
1539 text
1540 }
1541
1542 #[test]
1543 fn a_taken_branch_is_kept_and_the_other_is_not() {
1544 assert_eq!(clean("#if 1\nyes\n#else\nno\n#endif\n"), "yes");
1545 assert_eq!(clean("#if 0\nyes\n#else\nno\n#endif\n"), "no");
1546 }
1547
1548 #[test]
1549 fn ifdef_and_ifndef_ask_the_macro_table() {
1550 assert_eq!(clean("#define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
1551 assert_eq!(clean("#ifdef F\nyes\n#endif\n"), "");
1552 assert_eq!(clean("#ifndef F\nyes\n#endif\n"), "yes");
1553 assert_eq!(clean("#define F 1\n#if 0\na\n#elifdef F\nb\n#endif\n"), "b");
1555 assert_eq!(clean("#if 0\na\n#elifndef F\nb\n#endif\n"), "b");
1556 }
1557
1558 #[test]
1559 fn only_the_first_true_branch_of_a_chain_is_taken() {
1560 assert_eq!(clean("#if 0\na\n#elif 1\nb\n#elif 1\nc\n#else\nd\n#endif\n"), "b");
1561 assert_eq!(clean("#if 0\na\n#elif 0\nb\n#else\nc\n#endif\n"), "c");
1562 }
1563
1564 #[test]
1565 fn a_branch_after_one_that_was_taken_is_not_evaluated() {
1566 assert_eq!(clean("#if 1\na\n#elif 1/0\nb\n#endif\n"), "a");
1569 }
1570
1571 #[test]
1572 fn a_skipped_region_is_not_read_for_anything_but_nesting() {
1573 let src = "#if 0\nthis is not C at all\n#frobnicate\n#define\n#if 1\ninner\n#endif\n#endif\nafter\n";
1575 assert_eq!(clean(src), "after");
1576 }
1577
1578 #[test]
1579 fn nesting_inside_a_dead_branch_stays_balanced() {
1580 let src = "#if 0\n#ifdef X\na\n#else\nb\n#endif\n#else\nc\n#endif\n";
1581 assert_eq!(clean(src), "c");
1582 }
1583
1584 #[test]
1585 fn defined_works_in_both_spellings_and_before_expansion() {
1586 assert_eq!(clean("#define F 0\n#if defined F\nyes\n#endif\n"), "yes");
1587 assert_eq!(clean("#define F 0\n#if defined(F)\nyes\n#endif\n"), "yes");
1588 assert_eq!(clean("#if defined(F)\nyes\n#endif\n"), "");
1589 assert_eq!(clean("#define F 0\n#if defined F && !F\nyes\n#endif\n"), "yes");
1592 }
1593
1594 #[test]
1595 fn an_identifier_that_survived_expansion_is_zero() {
1596 assert_eq!(clean("#if NOT_DEFINED_ANYWHERE\nyes\n#else\nno\n#endif\n"), "no");
1597 assert_eq!(clean("#if !NOT_DEFINED_ANYWHERE\nyes\n#endif\n"), "yes");
1598 }
1599
1600 #[test]
1601 fn short_circuiting_keeps_a_guarded_expression_safe() {
1602 assert_eq!(clean("#if defined(F) && 1/F\nyes\n#else\nno\n#endif\n"), "no");
1605 assert_eq!(clean("#if 1 ? 2 : 1/0\nyes\n#endif\n"), "yes");
1606 }
1607
1608 #[test]
1609 fn the_operators_have_the_precedence_they_do_in_c() {
1610 assert_eq!(clean("#if 1 + 2 * 3 == 7\nyes\n#endif\n"), "yes");
1611 assert_eq!(clean("#if (1 + 2) * 3 == 9\nyes\n#endif\n"), "yes");
1612 assert_eq!(clean("#if 1 << 4 == 16\nyes\n#endif\n"), "yes");
1613 assert_eq!(clean("#if -8 / 3 == -2\nyes\n#endif\n"), "yes");
1614 assert_eq!(clean("#if (0xff & 0x0f) == 15\nyes\n#endif\n"), "yes");
1615 }
1616
1617 #[test]
1618 fn an_unsigned_operand_makes_the_whole_comparison_unsigned() {
1619 assert_eq!(clean("#if -1 < 0u\nyes\n#else\nno\n#endif\n"), "no");
1623 assert_eq!(clean("#if -1 < 0\nyes\n#else\nno\n#endif\n"), "yes");
1624 }
1625
1626 #[test]
1627 fn character_constants_evaluate() {
1628 assert_eq!(clean("#if 'A' == 65\nyes\n#endif\n"), "yes");
1629 assert_eq!(clean("#if '\\n' == 10\nyes\n#endif\n"), "yes");
1630 }
1631
1632 #[test]
1633 fn a_macro_is_expanded_before_the_expression_is_evaluated() {
1634 assert_eq!(clean("#define V 3\n#if V > 2\nyes\n#endif\n"), "yes");
1635 assert_eq!(clean("#define M(a) ((a) * 2)\n#if M(3) == 6\nyes\n#endif\n"), "yes");
1636 }
1637
1638 #[test]
1639 fn an_invocation_may_span_lines_within_a_run_of_text() {
1640 assert_eq!(clean("#define M(a, b) a + b\nM(1,\n2)\n"), "1 + 2");
1641 }
1642
1643 #[test]
1644 fn undef_removes_a_definition() {
1645 assert_eq!(clean("#define F 1\n#undef F\n#ifdef F\nyes\n#else\nno\n#endif\n"), "no");
1646 assert_eq!(clean("#undef NEVER_DEFINED\nok\n"), "ok");
1649 }
1650
1651 #[test]
1652 fn some_names_cannot_be_undefined() {
1653 let mut run = Run::new();
1654 run.go("#undef defined\n");
1655 assert_eq!(run.messages(), vec!["`defined` cannot be undefined".to_owned()]);
1656 }
1657
1658 #[test]
1659 fn error_reports_the_rest_of_the_line() {
1660 let mut run = Run::new();
1661 run.go("#if 0\n#error not this one\n#else\n#error unsupported target\n#endif\n");
1662 assert_eq!(run.messages(), vec!["unsupported target".to_owned()]);
1663 }
1664
1665 #[test]
1666 fn warning_is_a_warning() {
1667 let mut run = Run::new();
1668 run.go("#warning this is fine\n");
1669 assert_eq!(run.severities(), vec![Severity::Warning]);
1670 assert_eq!(run.messages(), vec!["this is fine".to_owned()]);
1671 }
1672
1673 #[test]
1674 fn an_unterminated_conditional_is_reported() {
1675 let mut run = Run::new();
1676 assert_eq!(run.go("#if 1\nyes\n"), "yes");
1677 assert_eq!(run.messages(), vec!["unterminated `#if`".to_owned()]);
1678 }
1679
1680 #[test]
1681 fn a_conditional_without_an_if_is_reported() {
1682 let mut run = Run::new();
1683 run.go("#endif\n");
1684 assert_eq!(run.messages(), vec!["`#endif` without `#if`".to_owned()]);
1685
1686 let mut run = Run::new();
1687 run.go("#if 1\n#else\n#else\n#endif\n");
1688 assert_eq!(run.messages(), vec!["a second `#else`".to_owned()]);
1689
1690 let mut run = Run::new();
1691 run.go("#if 1\n#else\n#elif 1\n#endif\n");
1692 assert_eq!(run.messages(), vec!["`#elif` after `#else`".to_owned()]);
1693 }
1694
1695 #[test]
1696 fn tokens_after_endif_are_a_warning_rather_than_an_error() {
1697 let mut run = Run::new();
1700 assert_eq!(run.go("#if 1\nyes\n#endif FOO\n"), "yes");
1701 assert_eq!(run.severities(), vec![Severity::Warning]);
1702 assert_eq!(run.messages(), vec!["extra tokens after `#endif`".to_owned()]);
1703 }
1704
1705 #[test]
1706 fn the_null_directive_does_nothing() {
1707 assert_eq!(clean("#\na\n#\nb\n"), "a b");
1708 }
1709
1710 #[test]
1711 fn an_unknown_directive_is_an_error_when_the_region_is_live() {
1712 let mut run = Run::new();
1713 run.go("#frobnicate\n");
1714 assert_eq!(run.messages(), vec!["invalid preprocessing directive".to_owned()]);
1715 }
1716
1717 #[test]
1718 fn line_is_recorded_for_the_source_map() {
1719 let mut run = Run::new();
1720 run.go("#line 42 \"other.c\"\n");
1721 assert!(run.messages().is_empty());
1722 let recorded = run.pp.line_directives();
1723 assert_eq!(recorded.len(), 1);
1724 assert_eq!(recorded[0].line, 42);
1725 let file = recorded[0].file.expect("a file name was given");
1726 assert_eq!(run.interner.resolve(file), "\"other.c\"");
1727 }
1728
1729 #[test]
1730 fn a_line_number_out_of_range_is_refused() {
1731 let mut run = Run::new();
1732 run.go("#line 0\n");
1733 assert_eq!(run.messages(), vec!["`#line` number is out of range".to_owned()]);
1734
1735 let mut run = Run::new();
1736 run.go("#line notanumber\n");
1737 assert_eq!(run.messages(), vec!["`#line` needs a decimal line number".to_owned()]);
1738 }
1739
1740 #[test]
1741 fn a_pragma_passes_through_unchanged() {
1742 assert_eq!(clean("#pragma pack(1)\nint x;\n"), "#pragma pack(1) int x;");
1743 }
1744
1745 #[test]
1746 fn the_pragma_operator_becomes_a_pragma() {
1747 assert_eq!(
1748 clean("_Pragma(\"GCC visibility push(default)\")\nint x;\n"),
1749 "#pragma GCC visibility push(default) int x;"
1750 );
1751 }
1752
1753 #[test]
1754 fn the_pragma_operator_works_from_inside_a_macro() {
1755 let src = "#define PUSH _Pragma(\"pack(push)\")\nPUSH\nint x;\n";
1758 assert_eq!(clean(src), "#pragma pack(push) int x;");
1759 }
1760
1761 #[test]
1762 fn a_pragma_operator_that_is_not_given_a_string_is_reported() {
1763 let mut run = Run::new();
1764 run.go("_Pragma(x)\n");
1765 assert_eq!(run.messages(), vec!["`_Pragma` takes a single string literal".to_owned()]);
1766 }
1767
1768 #[test]
1769 fn an_include_reads_the_file_it_names() {
1770 let mut run = Run::new();
1771 run.file("/dir/one.h", "int from_the_header;\n");
1772 run.dir("/dir");
1773 assert_eq!(run.go("#include <one.h>\nint after;\n"), "int from_the_header; int after;");
1774 assert!(run.messages().is_empty());
1775 }
1776
1777 #[test]
1778 fn a_quoted_include_looks_next_to_the_including_file_first() {
1779 let mut run = Run::new();
1780 run.file("/local.h", "beside\n");
1781 run.file("/dir/local.h", "on the path\n");
1782 run.dir("/dir");
1783 assert_eq!(run.go("#include \"local.h\"\n"), "beside");
1784 assert!(run.messages().is_empty());
1785 }
1786
1787 #[test]
1788 fn an_angled_include_does_not_look_next_to_the_including_file() {
1789 let mut run = Run::new();
1790 run.file("/local.h", "beside\n");
1791 run.file("/dir/local.h", "on the path\n");
1792 run.dir("/dir");
1793 assert_eq!(run.go("#include <local.h>\n"), "on the path");
1794 }
1795
1796 #[test]
1797 fn a_macro_defined_in_a_header_is_visible_after_the_include() {
1798 let mut run = Run::new();
1799 run.file("/dir/defs.h", "#define N 42\n");
1800 run.dir("/dir");
1801 assert_eq!(run.go("#include <defs.h>\nint a = N;\n"), "int a = 42;");
1802 assert!(run.messages().is_empty());
1803 }
1804
1805 #[test]
1806 fn an_include_guard_keeps_the_second_read_empty() {
1807 let mut run = Run::new();
1808 run.file("/dir/g.h", "#ifndef G\n#define G\nonce\n#endif\n");
1809 run.dir("/dir");
1810 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
1811 assert!(run.messages().is_empty());
1812 assert_eq!(run.files(), 2, "the second include is not opened at all");
1813 }
1814
1815 #[test]
1816 fn the_other_spelling_of_a_guard_is_recognised_too() {
1817 for guard in ["#if !defined(G)", "#if !defined G"] {
1818 let mut run = Run::new();
1819 run.file("/dir/g.h", &format!("{guard}\n#define G\nonce\n#endif\n"));
1820 run.dir("/dir");
1821 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "once");
1822 assert_eq!(run.files(), 2, "{guard} should be a guard");
1823 }
1824 }
1825
1826 #[test]
1827 fn a_conditional_that_is_not_a_guard_does_not_skip_anything() {
1828 let mut run = Run::new();
1831 run.file("/dir/g.h", "#ifndef G\ntwice\n#endif\n");
1832 run.dir("/dir");
1833 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "twice twice");
1834 assert_eq!(run.files(), 3);
1835 }
1836
1837 #[test]
1838 fn a_token_outside_the_guard_stops_it_being_a_guard() {
1839 let mut run = Run::new();
1840 run.file("/dir/g.h", "#ifndef G\n#define G\n#endif\nalways\n");
1841 run.dir("/dir");
1842 assert_eq!(run.go("#include <g.h>\n#include <g.h>\n"), "always always");
1843 assert_eq!(run.files(), 3);
1844 }
1845
1846 #[test]
1847 fn pragma_once_skips_the_second_read_and_does_not_reach_the_output() {
1848 let mut run = Run::new();
1849 run.file("/dir/o.h", "#pragma once\nonce\n");
1850 run.dir("/dir");
1851 assert_eq!(run.go("#include <o.h>\n#include <o.h>\n"), "once");
1852 assert!(run.messages().is_empty());
1853 assert_eq!(run.files(), 2);
1854 }
1855
1856 #[test]
1857 fn pragma_once_in_the_main_file_is_a_warning() {
1858 let mut run = Run::new();
1861 assert_eq!(run.go("#pragma once\nx\n"), "x");
1862 assert_eq!(run.severities(), vec![Severity::Warning]);
1863 assert_eq!(run.messages(), vec!["`#pragma once` in the main file".to_owned()]);
1864 }
1865
1866 #[test]
1867 fn any_other_pragma_still_passes_through() {
1868 assert_eq!(clean("#pragma once_upon_a_time\n"), "#pragma once_upon_a_time");
1869 }
1870
1871 #[test]
1872 fn has_include_answers_from_the_search_path() {
1873 let mut run = Run::new();
1874 run.file("/dir/there.h", "");
1875 run.dir("/dir");
1876 let src = "#if __has_include(<there.h>)\nyes\n#endif\n\
1877 #if __has_include(<gone.h>)\nno\n#endif\n";
1878 assert_eq!(run.go(src), "yes");
1879 assert!(run.messages().is_empty(), "a header that is not there is an answer, not an error");
1880 }
1881
1882 #[test]
1883 fn has_include_asks_the_question_the_include_on_the_same_line_would() {
1884 let mut run = Run::new();
1888 run.file("/beside.h", "");
1889 let src = "#if __has_include(\"beside.h\")\nquoted\n#endif\n\
1890 #if __has_include(<beside.h>)\nangled\n#endif\n";
1891 assert_eq!(run.go(src), "quoted");
1892 }
1893
1894 #[test]
1895 fn has_include_next_starts_where_include_next_would() {
1896 let mut run = Run::new();
1897 run.file("/a/both.h", "#if __has_include_next(<both.h>)\nmore\n#endif\n");
1898 run.file("/b/both.h", "last\n");
1899 run.file("/a/only.h", "#if __has_include_next(<only.h>)\nmore\n#endif\n");
1900 run.dir("/a");
1901 run.dir("/b");
1902 assert_eq!(run.go("#include <both.h>\n"), "more");
1903 assert_eq!(run.go("#include <only.h>\n"), "", "there is nothing after /a to find it in");
1904 }
1905
1906 #[test]
1907 fn the_operand_of_has_include_is_not_macro_expanded() {
1908 let mut run = Run::new();
1911 run.file("/dir/linux/version.h", "");
1912 run.dir("/dir");
1913 let src = "#define linux 1\n#if __has_include(<linux/version.h>)\nyes\n#endif\n";
1914 assert_eq!(run.go(src), "yes");
1915 }
1916
1917 #[test]
1918 fn a_macro_may_expand_to_a_has_include() {
1919 let mut run = Run::new();
1921 run.file("/dir/there.h", "");
1922 run.dir("/dir");
1923 let src = "#define HAVE __has_include(<there.h>)\n#if HAVE\nyes\n#endif\n";
1924 assert_eq!(run.go(src), "yes");
1925 }
1926
1927 #[test]
1928 fn defined_says_the_has_operators_are_there() {
1929 let src = "#if defined(__has_include) && defined __has_builtin\nyes\n#endif\n";
1932 assert_eq!(clean(src), "yes");
1933 assert_eq!(clean("#ifdef __has_attribute\nyes\n#endif\n"), "yes");
1934 }
1935
1936 #[test]
1937 fn has_attribute_answers_out_of_the_matrix() {
1938 assert_eq!(clean("#if __has_attribute(packed)\nyes\n#endif\n"), "");
1941 assert_eq!(clean("#if __has_attribute(no_such_attribute)\nyes\n#endif\n"), "");
1942 assert_eq!(clean("#if !__has_attribute(packed)\nno\n#endif\n"), "no");
1943 }
1944
1945 #[test]
1946 fn the_scoped_spelling_of_an_attribute_is_the_same_question() {
1947 assert_eq!(clean("#if __has_c_attribute(gnu::packed)\nyes\n#endif\n"), "");
1951 assert_eq!(clean("#if __has_c_attribute(deprecated)\nyes\n#endif\n"), "");
1952 }
1953
1954 #[test]
1955 fn has_builtin_answers_no_until_the_builtin_is_real() {
1956 assert_eq!(clean("#if __has_builtin(__builtin_expect)\nyes\n#endif\n"), "");
1957 assert_eq!(clean("#if __has_builtin(__builtin_nonesuch)\nyes\n#endif\n"), "");
1958 }
1959
1960 #[test]
1961 fn has_feature_and_has_extension_read_the_same_table() {
1962 assert_eq!(clean("#if __has_feature(pragma_once)\nyes\n#endif\n"), "yes");
1965 assert_eq!(clean("#if __has_extension(pragma_once)\nyes\n#endif\n"), "yes");
1966 assert_eq!(clean("#if __has_extension(include_next)\nyes\n#endif\n"), "yes");
1967 assert_eq!(clean("#if __has_feature(include_next)\nyes\n#endif\n"), "");
1968 assert_eq!(clean("#if __has_feature(statement_expressions)\nyes\n#endif\n"), "");
1969 }
1970
1971 #[test]
1972 fn building_module_is_always_no_and_is_recognised_so_that_the_line_parses() {
1973 assert_eq!(clean("#if __building_module(m)\nyes\n#endif\n"), "");
1977 assert_eq!(clean("#if !__building_module(m)\nyes\n#endif\n"), "yes");
1978 assert_eq!(
1979 clean(
1980 "#if !defined(offsetof) || (__has_feature(modules) && !__building_module(x))\nyes\n#endif\n"
1981 ),
1982 "yes"
1983 );
1984 assert_eq!(clean("#ifdef __building_module\nyes\n#endif\n"), "yes");
1986 assert_eq!(clean("#if defined(__building_module)\nyes\n#endif\n"), "yes");
1987 }
1988
1989 #[test]
1990 fn a_has_operator_without_an_operand_is_reported() {
1991 let mut run = Run::new();
1992 run.go("#if __has_include\nyes\n#endif\n");
1993 assert_eq!(run.messages(), ["expected `(` after `__has_include`"]);
1994 let mut run = Run::new();
1995 run.go("#if __has_include(1)\nyes\n#endif\n");
1996 assert_eq!(run.messages(), ["expected a file name in `<>` or `\"\"`"]);
1997 let mut run = Run::new();
1998 run.go("#if __has_attribute(\"packed\")\nyes\n#endif\n");
1999 assert_eq!(run.messages(), ["expected an identifier as the operand of `__has_attribute`"]);
2000 }
2001
2002 #[test]
2003 fn the_predefined_set_is_visible_to_the_source_file() {
2004 let mut run = Run::new();
2005 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2006 let src = "#if defined(__x86_64__) && defined(__linux__) && __SIZEOF_LONG__ == 8\n\
2007 yes\n#endif\n";
2008 assert_eq!(run.go(src), "yes");
2009 assert!(run.messages().is_empty());
2010 }
2011
2012 #[test]
2013 fn the_predefined_set_follows_the_target_and_not_the_host() {
2014 let mut run = Run::new();
2015 run.predefine("aarch64-unknown-linux-gnu", &Predef::new());
2016 assert_eq!(
2017 run.go("#ifdef __x86_64__\nno\n#endif\n#ifdef __aarch64__\nyes\n#endif\n"),
2018 "yes"
2019 );
2020 }
2021
2022 #[test]
2023 fn a_predefined_macro_expands_where_it_is_used() {
2024 let mut run = Run::new();
2025 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2026 assert_eq!(run.go("__SIZE_TYPE__ n;\n"), "long unsigned int n;");
2027 }
2028
2029 #[test]
2030 fn a_command_line_define_is_a_definition_like_any_other() {
2031 let mut opts = Predef::new();
2032 opts.defines = vec!["FOO".to_owned(), "BAR=3".to_owned()];
2033 opts.undefines = vec!["__linux__".to_owned()];
2034 let mut run = Run::new();
2035 run.predefine("x86_64-unknown-linux-gnu", &opts);
2036 let src = "#if FOO && BAR == 3 && !defined(__linux__)\nyes\n#endif\n";
2037 assert_eq!(run.go(src), "yes");
2038 assert!(run.messages().is_empty());
2039 }
2040
2041 #[test]
2042 fn the_predefined_set_produces_no_tokens_of_its_own() {
2043 let mut run = Run::new();
2046 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2047 assert_eq!(run.go("alone\n"), "alone");
2048 }
2049
2050 #[test]
2051 fn the_predefined_files_are_named_the_way_gcc_names_them() {
2052 let mut run = Run::new();
2053 let mut opts = Predef::new();
2054 opts.defines = vec!["FOO=1".to_owned()];
2055 run.predefine("x86_64-unknown-linux-gnu", &opts);
2056 let names: Vec<&str> = run.sources.files().iter().map(|f| f.name.as_str()).collect();
2057 assert_eq!(names, ["<built-in>", "<command-line>"]);
2058 }
2059
2060 #[test]
2061 fn a_dialect_without_the_gnu_extensions_says_so() {
2062 let mut opts = Predef::new();
2063 opts.gnu_extensions = false;
2064 opts.std = Std::C99;
2065 let mut run = Run::new();
2066 run.predefine("x86_64-unknown-linux-gnu", &opts);
2067 let src = "#if defined(__STRICT_ANSI__) && __STDC_VERSION__ == 199901L && !defined(linux)\n\
2068 yes\n#endif\n";
2069 assert_eq!(run.go(src), "yes");
2070 }
2071
2072 #[test]
2073 fn the_date_and_time_are_the_same_for_the_whole_translation_unit() {
2074 let mut opts = Predef::new();
2075 opts.timestamp = Timestamp::from_unix(0);
2076 let mut run = Run::new();
2077 run.predefine("x86_64-unknown-linux-gnu", &opts);
2078 assert_eq!(run.go("__DATE__ __TIME__\n"), "\"Jan 1 1970\" \"00:00:00\"");
2079 }
2080
2081 #[test]
2082 fn a_has_operator_in_a_dead_branch_is_not_asked_about() {
2083 assert_eq!(clean("#if 0\n#if __has_include\n#endif\n#endif\nafter\n"), "after");
2085 }
2086
2087 #[test]
2088 fn a_conditional_may_not_span_an_include() {
2089 let mut run = Run::new();
2093 run.file("/dir/open.h", "#if 1\n");
2094 run.dir("/dir");
2095 run.go("#include <open.h>\nkept\n#endif\n");
2096 let messages = run.messages();
2097 assert_eq!(messages.len(), 2);
2098 assert!(messages[0].contains("unterminated"));
2099 assert!(messages[1].contains("without"));
2100 }
2101
2102 #[test]
2103 fn include_next_continues_after_the_directory_the_file_came_from() {
2104 let mut run = Run::new();
2107 run.file("/a/limits.h", "wrapper\n#include_next <limits.h>\n");
2108 run.file("/b/limits.h", "real\n");
2109 run.dir("/a");
2110 run.dir("/b");
2111 assert_eq!(run.go("#include <limits.h>\n"), "wrapper real");
2112 assert!(run.messages().is_empty());
2113 }
2114
2115 #[test]
2116 fn a_computed_include_is_expanded_first() {
2117 let mut run = Run::new();
2118 run.file("/dir/sub/thing.h", "computed\n");
2119 run.dir("/dir");
2120 let src = "#define HEADER <sub/thing.h>\n#include HEADER\n";
2121 assert_eq!(run.go(src), "computed");
2122 assert!(run.messages().is_empty());
2123 let mut run = Run::new();
2125 run.file("/dir/sub/thing.h", "computed\n");
2126 run.dir("/dir");
2127 assert_eq!(run.go("#define H \"sub/thing.h\"\n#include H\n"), "computed");
2128 }
2129
2130 #[test]
2131 fn a_header_that_is_not_there_says_where_it_looked() {
2132 let mut run = Run::new();
2133 run.dir("/dir");
2134 run.go("#include <nope.h>\n");
2135 let diagnostics = run.pp.take_diagnostics();
2136 assert_eq!(diagnostics.len(), 1);
2137 assert_eq!(diagnostics[0].code, Some("E0341"));
2138 assert_eq!(diagnostics[0].message, "`nope.h` file not found");
2139 assert!(diagnostics[0].children[0].message.contains("/dir"));
2140 }
2141
2142 #[test]
2143 fn an_include_that_is_not_a_header_name_is_reported() {
2144 let mut run = Run::new();
2145 run.go("#include 3\n");
2146 let diagnostics = run.pp.take_diagnostics();
2147 assert_eq!(diagnostics[0].code, Some("E0343"));
2148 }
2149
2150 #[test]
2151 fn a_header_that_includes_itself_stops() {
2152 let mut run = Run::new();
2153 run.file("/dir/loop.h", "#include <loop.h>\n");
2154 run.dir("/dir");
2155 run.go("#include <loop.h>\n");
2156 let diagnostics = run.pp.take_diagnostics();
2157 assert_eq!(diagnostics.len(), 1, "one complaint, not one per level");
2158 assert_eq!(diagnostics[0].code, Some("E0342"));
2159 }
2160
2161 #[test]
2162 fn an_include_in_a_dead_branch_is_not_read() {
2163 let mut run = Run::new();
2164 assert_eq!(run.go("#if 0\n#include <nothing.h>\n#endif\nafter\n"), "after");
2165 assert!(run.messages().is_empty(), "a skipped include is not resolved");
2166 }
2167
2168 #[test]
2169 fn embed_writes_the_bytes_of_the_resource() {
2170 let mut run = Run::new();
2171 run.bytes("/logo.bin", &[0, 1, 127, 128, 255]);
2172 assert_eq!(run.go("#embed \"logo.bin\"\n"), "0, 1, 127, 128, 255");
2173 assert!(run.messages().is_empty());
2174 }
2175
2176 #[test]
2177 fn an_embed_is_a_valid_initializer_on_both_sides_of_empty() {
2178 let mut run = Run::new();
2183 run.bytes("/some.bin", &[7, 8]);
2184 run.bytes("/none.bin", &[]);
2185 let line = |name: &str| {
2186 format!("{{\n#embed \"{name}\" prefix(0xEF,) suffix(,0xFE) if_empty(0)\n}}\n")
2187 };
2188 assert_eq!(run.go(&line("some.bin")), "{ 0xEF,7, 8 ,0xFE }");
2189 assert_eq!(run.go_named("/other.c", &line("none.bin")), "{ 0 }");
2190 assert!(run.messages().is_empty());
2191 }
2192
2193 #[test]
2194 fn the_limit_and_the_offset_choose_a_window_of_the_resource() {
2195 let mut run = Run::new();
2196 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
2197 assert_eq!(run.go("#embed \"eight.bin\" limit(3)\n"), "1, 2, 3");
2198 assert_eq!(
2199 run.go_named("/b.c", "#embed \"eight.bin\" gnu::offset(4) limit(3)\n"),
2200 "5, 6, 7"
2201 );
2202 assert_eq!(run.go_named("/c.c", "#embed \"eight.bin\" limit(0) if_empty(9)\n"), "9");
2205 assert_eq!(run.go_named("/d.c", "#embed \"eight.bin\" gnu::offset(99)\n"), "");
2206 assert!(run.messages().is_empty());
2207 }
2208
2209 #[test]
2210 fn the_limit_is_a_constant_expression_and_not_just_a_number() {
2211 let mut run = Run::new();
2214 run.bytes("/eight.bin", &[1, 2, 3, 4, 5, 6, 7, 8]);
2215 assert_eq!(
2216 run.go("#define CHUNK 2\n#embed \"eight.bin\" limit(CHUNK * 2)\n"),
2217 "1, 2, 3, 4"
2218 );
2219 assert!(run.messages().is_empty());
2220 }
2221
2222 #[test]
2223 fn a_misspelled_embed_parameter_is_refused_rather_than_ignored() {
2224 let mut run = Run::new();
2227 run.bytes("/eight.bin", &[1, 2]);
2228 assert_eq!(run.go("#embed \"eight.bin\" limits(1)\n"), "");
2229 assert_eq!(run.messages(), vec!["unknown `#embed` parameter `limits`".to_owned()]);
2230 let mut vendor = Run::new();
2231 vendor.bytes("/eight.bin", &[1, 2]);
2232 assert_eq!(vendor.go("#embed \"eight.bin\" clang::offset(1)\n"), "");
2233 assert_eq!(
2234 vendor.messages(),
2235 vec!["unknown `#embed` parameter `clang::offset`".to_owned()]
2236 );
2237 }
2238
2239 #[test]
2240 fn a_missing_embed_resource_is_reported_as_a_resource() {
2241 let mut run = Run::new();
2242 run.go("#embed <nothing.bin>\n");
2243 assert_eq!(run.messages(), vec!["`nothing.bin` resource not found".to_owned()]);
2244 }
2245
2246 #[test]
2247 fn has_embed_tells_missing_from_present_from_empty() {
2248 let mut run = Run::new();
2252 run.bytes("/some.bin", &[1]);
2253 run.bytes("/none.bin", &[]);
2254 let src = "#if __has_embed(\"none.bin\") == __STDC_EMBED_EMPTY__\nempty\n#endif\n\
2255 #if __has_embed(\"some.bin\") == __STDC_EMBED_FOUND__\nfound\n#endif\n\
2256 #if __has_embed(\"gone.bin\") == __STDC_EMBED_NOT_FOUND__\ngone\n#endif\n";
2257 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
2258 assert_eq!(run.go(src), "empty found gone");
2259 assert!(run.messages().is_empty());
2260 }
2261
2262 #[test]
2263 fn has_embed_takes_the_limit_into_account() {
2264 let mut run = Run::new();
2267 run.bytes("/some.bin", &[1, 2, 3]);
2268 run.predefine("x86_64-unknown-linux-gnu", &Predef::default());
2269 let src = "#if __has_embed(\"some.bin\" limit(0)) == __STDC_EMBED_EMPTY__\nempty\n#endif\n";
2270 assert_eq!(run.go(src), "empty");
2271 assert!(run.messages().is_empty());
2272 }
2273
2274 #[test]
2275 fn a_directive_may_have_space_before_the_hash_and_after_it() {
2276 assert_eq!(clean(" # define F 1\n#ifdef F\nyes\n#endif\n"), "yes");
2277 }
2278
2279 #[test]
2280 fn a_definition_survives_across_a_conditional() {
2281 assert_eq!(clean("#if 1\n#define F 7\n#endif\nF\n"), "7");
2282 }
2283
2284 #[test]
2285 fn an_empty_if_expression_is_reported() {
2286 let mut run = Run::new();
2287 run.go("#if\n#endif\n");
2288 assert_eq!(run.messages(), vec!["`#if` with no expression".to_owned()]);
2289 }
2290
2291 #[test]
2292 fn the_file_and_the_line_say_where_the_use_is() {
2293 let mut run = Run::new();
2294 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2295 assert_eq!(run.go("__FILE__ __LINE__\n__LINE__\n"), "\"/main.c\" 1 2");
2296 assert!(run.messages().is_empty());
2297 }
2298
2299 #[test]
2300 fn a_macro_that_mentions_the_line_answers_with_the_call() {
2301 let mut run = Run::new();
2302 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2303 run.file("/where.h", "#define WHERE __FILE__ __LINE__\n");
2304 assert_eq!(run.go("#include \"where.h\"\n\n\nWHERE\n"), "\"/main.c\" 4");
2308 assert!(run.messages().is_empty());
2309 }
2310
2311 #[test]
2312 fn the_file_name_is_the_file_without_the_directories() {
2313 let mut run = Run::new();
2314 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2315 assert_eq!(run.go_named("/deep/down/main.c", "__FILE_NAME__\n"), "\"main.c\"");
2316 }
2317
2318 #[test]
2319 fn a_backslash_in_the_name_is_escaped() {
2320 let mut run = Run::new();
2321 run.predefine("x86_64-pc-windows-msvc", &Predef::new());
2322 let text = run.go_named("C:\\src\\main.c", "__FILE__ __FILE_NAME__\n");
2325 assert_eq!(text, "\"C:\\\\src\\\\main.c\" \"main.c\"");
2326 }
2327
2328 #[test]
2329 fn the_base_file_is_the_one_named_on_the_command_line() {
2330 let mut run = Run::new();
2331 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2332 run.file("/deep.h", "__FILE__ __BASE_FILE__\n");
2333 assert_eq!(run.go("#include \"deep.h\"\n"), "\"/deep.h\" \"/main.c\"");
2334 assert!(run.messages().is_empty());
2335 }
2336
2337 #[test]
2338 fn the_include_level_counts_the_headers_above_it() {
2339 let mut run = Run::new();
2340 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2341 run.file("/one.h", "__INCLUDE_LEVEL__\n#include \"two.h\"\n");
2342 run.file("/two.h", "__INCLUDE_LEVEL__\n");
2343 assert_eq!(run.go("__INCLUDE_LEVEL__\n#include \"one.h\"\n"), "0 1 2");
2344 assert!(run.messages().is_empty());
2345 }
2346
2347 #[test]
2348 fn the_counter_is_a_different_number_every_time() {
2349 let mut run = Run::new();
2350 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2351 assert_eq!(run.go("__COUNTER__ __COUNTER__ __COUNTER__\n"), "0 1 2");
2352 }
2353
2354 #[test]
2355 fn the_counter_advances_once_per_argument_rather_than_once_per_use() {
2356 let mut run = Run::new();
2357 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2358 assert_eq!(run.go("#define TWICE(x) x x\nTWICE(__COUNTER__) __COUNTER__\n"), "0 0 1");
2362 }
2363
2364 #[test]
2365 fn the_line_is_a_number_an_if_can_use() {
2366 let mut run = Run::new();
2367 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2368 assert_eq!(run.go("#if __LINE__ == 1 && __INCLUDE_LEVEL__ == 0\nyes\n#endif\n"), "yes");
2369 assert!(run.messages().is_empty());
2370 }
2371
2372 #[test]
2373 fn the_dynamic_macros_are_defined_like_any_others() {
2374 let mut run = Run::new();
2375 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2376 let src = "#ifdef __FILE__\nyes\n#endif\n#undef __LINE__\n#ifndef __LINE__\ngone\n#endif\n";
2377 assert_eq!(run.go(src), "yes gone");
2378 assert!(run.messages().is_empty(), "`#undef` of a builtin is allowed, as it is in GCC");
2379 }
2380
2381 #[test]
2382 fn redefining_a_dynamic_macro_warns_and_points_at_the_built_in_file() {
2383 let mut run = Run::new();
2384 run.predefine("x86_64-unknown-linux-gnu", &Predef::new());
2385 assert_eq!(run.go("#define __FILE__ \"mine.c\"\n__FILE__\n"), "\"mine.c\"");
2386 let complaints = run.pp.take_diagnostics();
2387 assert_eq!(complaints.len(), 1);
2388 assert_eq!(complaints[0].code, Some("W0301"));
2389 let previous = complaints[0].children.first().expect("a note saying where it was");
2390 assert_eq!(run.sources.lookup(previous.span.lo).map(|loc| loc.file), {
2391 let built_in = run.sources.files().iter().find(|f| f.name == BUILT_IN);
2392 built_in.map(|f| f.id)
2393 });
2394 }
2395
2396 #[test]
2397 fn destringizing_undoes_what_stringizing_did() {
2398 assert_eq!(destringize(r#""a \"b\" c""#), r#"a "b" c"#);
2399 assert_eq!(destringize(r#""a \\ b""#), r"a \ b");
2400 assert_eq!(destringize(r#"L"wide""#), "wide");
2401 }
2402}