Skip to main content

rucc_pp/
lib.rs

1//! Macro expansion, conditionals, include resolution, and the header cache.
2//!
3//! Design: `spec/05-preprocessor.md`. Layer rank 5, see `spec/18-package-layout.md`.
4//!
5//! # Status
6//!
7//! Macro expansion is implemented: object-like and function-like macros, `#` and `##`,
8//! variadics in both the standard and the GNU spelling, `__VA_OPT__`, and the GNU comma
9//! swallowing extension, all on hide sets rather than on a depth counter.
10//!
11//! Every token that comes out of a macro carries the chain of macros it came out of, so an
12//! error inside a macro three headers deep prints the way to it rather than only the two ends
13//! of it. The chain is interned, so a hundred token replacement list costs one node.
14//!
15//! Directives are implemented: `#define`, `#undef`, the whole conditional family with the
16//! `#if` expression evaluator, `#error`, `#warning`, `#line`, `#pragma`, the `_Pragma`
17//! operator, and `#include` and `#include_next` against a search path that follows GCC's
18//! order. `#embed` produces its bytes as tokens, and the fast path that avoids making
19//! them at all waits on the parser.
20//!
21//! A header is read once. `#pragma once` and the multiple include optimization, which spots
22//! the ordinary `#ifndef` wrapper and skips the file rather than reading it and throwing the
23//! result away, both do that.
24//!
25//! The `__has_*` family is implemented. `__has_include` and `__has_include_next` ask the
26//! search path the same question the directive on the same line would ask it, and the rest
27//! answer out of the matrix in `rucc-gnu`, which means they answer no for almost everything
28//! until the parser lands. That is the point of them.
29//!
30//! The predefined macro set is generated from the target description rather than hardcoded,
31//! and arrives as two synthetic files, `<built-in>` and `<command-line>`, so that a
32//! diagnostic about one of them says where it came from. `__DATE__` and `__TIME__` are in it
33//! because they are fixed for a translation unit. The ones that are not fixed, `__FILE__`,
34//! `__FILE_NAME__`, `__BASE_FILE__`, `__LINE__`, `__INCLUDE_LEVEL__` and `__COUNTER__`, are
35//! answered by the expander out of the source map at the place they are used.
36//!
37//! `print` writes the token stream back out the way `-E` does, with GCC's line markers, GCC's
38//! blank line padding, the indentation the source had, and a space wherever two tokens would
39//! otherwise read back as one. `-P` turns the markers and the padding off.
40//!
41//! ```
42//! use rucc_base::Interner;
43//! use rucc_diag::SourceMap;
44//! use rucc_lex::PpTokenKind;
45//! use rucc_pp::{Context, Preprocessor};
46//! use rucc_session::{MemoryFileSystem, SearchPath};
47//!
48//! let mut fs = MemoryFileSystem::new();
49//! fs.insert("/square.h", b"#define SQUARE(x) ((x) * (x))\n".to_vec());
50//!
51//! let mut interner = Interner::new();
52//! let mut sources = SourceMap::new();
53//! let main = b"#include \"square.h\"\n#if SQUARE(2) == 4\nSQUARE(3)\n#endif\n";
54//! let file = sources.add("/main.c", main.to_vec())?;
55//!
56//! let search = SearchPath::new();
57//! let mut cx = Context::new(&mut interner, &mut sources, &fs, &search);
58//! let mut pp = Preprocessor::new();
59//! let out = pp.run(file, &mut cx);
60//! assert!(pp.diagnostics().is_empty());
61//!
62//! let spelled: Vec<&str> = out
63//!     .iter()
64//!     .map(|t| match t.kind {
65//!         PpTokenKind::Punct(p) => p.as_str(),
66//!         _ => interner.resolve(t.value.unwrap()),
67//!     })
68//!     .collect();
69//! assert_eq!(spelled.concat(), "((3)*(3))");
70//! # Ok::<(), rucc_diag::SourceMapFull>(())
71//! ```
72//!
73//! Every crate in the workspace is published, and publishing implies a promise. This one is
74//! tier 3: its Rust API is explicitly unstable and will change without a major version bump.
75//! Depend on the `rucc` binary's behaviour, not on this.
76
77#![doc(html_root_url = "https://docs.rs/rucc-pp/0.2.0")]
78
79mod cond;
80mod directive;
81mod dump;
82mod embed;
83mod expand;
84mod hide;
85mod include;
86mod macros;
87mod predef;
88mod print;
89mod token;
90mod trace;
91
92pub use crate::directive::{LineDirective, Preprocessor};
93pub use crate::dump::macros as dump_macros;
94pub use crate::expand::Expander;
95pub use crate::hide::{HideSet, HideSets};
96pub use crate::include::Context;
97pub use crate::macros::{Builtin, MacroDef, MacroTable, parse_define};
98pub use crate::predef::{BUILT_IN, COMMAND_LINE, Predef, Timestamp};
99// The version claim lives on `Options` so that the driver can set it, and is re-exported here
100// because this is the crate that turns it into `__GNUC__`.
101pub use crate::print::{PrintOptions, print};
102pub use crate::token::Tok;
103pub use crate::trace::{Step, TraceId, Traces};
104pub use rucc_session::GnucVersion;
105
106/// The milestone in `spec/17-milestones.md` that fills this crate in.
107pub const MILESTONE: &str = "M1";
108
109#[cfg(test)]
110mod tests {
111    use rucc_base::Interner;
112    use rucc_diag::{Diagnostic, SourceMap, Span};
113    use rucc_lex::{Options, PpToken, PpTokenKind, TokenFlags, tokenize};
114
115    use super::*;
116
117    /// A preprocessor with a macro table, wired up the way the directive layer will do it.
118    struct Pp {
119        interner: Interner,
120        macros: MacroTable,
121        expander: Expander,
122        /// Empty, because these tests are about substitution rather than about where a token
123        /// came from. The tests for the macros that ask that question live in `directive`,
124        /// where there is a file to ask about.
125        sources: SourceMap,
126    }
127
128    impl Pp {
129        fn new() -> Pp {
130            Pp {
131                interner: Interner::new(),
132                macros: MacroTable::new(),
133                expander: Expander::new(),
134                sources: SourceMap::new(),
135            }
136        }
137
138        fn lex(&mut self, src: &str) -> Vec<PpToken> {
139            let (tokens, errors) = tokenize(src.as_bytes(), 0, Options::new(), &mut self.interner);
140            assert!(errors.is_empty(), "test input should lex cleanly: {errors:?}");
141            tokens.into_iter().filter(|t| t.kind != PpTokenKind::Eof).collect()
142        }
143
144        /// `define("F(a) a + 1")`, that is, everything after the `#define`.
145        fn define(&mut self, line: &str) {
146            let tokens = self.lex(line);
147            let (def, errors) = parse_define(&tokens, &mut self.interner);
148            assert!(errors.is_empty(), "definition should be clean: {errors:?}");
149            self.macros.define(def.expect("should parse"), &self.interner);
150        }
151
152        fn undef(&mut self, name: &str) {
153            let sym = self.interner.intern(name);
154            self.macros.undef(sym);
155        }
156
157        fn expand(&mut self, src: &str) -> Vec<Tok> {
158            let tokens = self.lex(src);
159            self.expander.expand(&tokens, &self.macros, &mut self.interner, &self.sources)
160        }
161
162        /// The expansion, with one space wherever the tokens are separated. This is close to
163        /// what `-E` prints and it is what makes these tests readable next to the standard's
164        /// own examples.
165        fn text(&mut self, src: &str) -> String {
166            let out = self.expand(src);
167            let mut text = String::new();
168            for (at, tok) in out.iter().enumerate() {
169                if at > 0 && tok.flags.has(TokenFlags::LEADING_SPACE) {
170                    text.push(' ');
171                }
172                match tok.kind {
173                    PpTokenKind::Punct(p) => text.push_str(p.as_str()),
174                    _ => text.push_str(
175                        self.interner.resolve(tok.value.expect("every non-punctuator interns")),
176                    ),
177                }
178            }
179            text
180        }
181
182        fn errors(&mut self) -> Vec<Diagnostic> {
183            self.expander.take_diagnostics()
184        }
185    }
186
187    #[test]
188    fn an_object_like_macro_is_replaced_by_its_body() {
189        let mut pp = Pp::new();
190        pp.define("N 42");
191        assert_eq!(pp.text("int a = N;"), "int a = 42;");
192    }
193
194    #[test]
195    fn an_undefined_identifier_is_left_alone() {
196        let mut pp = Pp::new();
197        assert_eq!(pp.text("int a = N;"), "int a = N;");
198    }
199
200    #[test]
201    fn a_function_like_macro_needs_a_parenthesis_to_be_invoked() {
202        let mut pp = Pp::new();
203        pp.define("f(x) x");
204        assert_eq!(pp.text("f"), "f", "a bare name is an ordinary identifier");
205        assert_eq!(pp.text("f (1)"), "1", "whitespace before the parenthesis is fine");
206    }
207
208    #[test]
209    fn arguments_are_expanded_before_they_are_substituted() {
210        let mut pp = Pp::new();
211        pp.define("ONE 1");
212        pp.define("f(x) (x + x)");
213        assert_eq!(pp.text("f(ONE)"), "(1 + 1)");
214    }
215
216    #[test]
217    fn an_empty_argument_is_an_argument() {
218        let mut pp = Pp::new();
219        pp.define("f(x) [x]");
220        assert_eq!(pp.text("f()"), "[]");
221    }
222
223    #[test]
224    fn a_macro_with_no_parameters_takes_no_arguments() {
225        let mut pp = Pp::new();
226        pp.define("f() nothing");
227        assert_eq!(pp.text("f()"), "nothing");
228    }
229
230    #[test]
231    fn a_comma_inside_parentheses_does_not_split_an_argument() {
232        let mut pp = Pp::new();
233        pp.define("f(x) [x]");
234        assert_eq!(pp.text("f((1, 2))"), "[(1, 2)]");
235    }
236
237    #[test]
238    fn an_invocation_may_span_lines() {
239        let mut pp = Pp::new();
240        pp.define("f(a, b) a b");
241        assert_eq!(pp.text("f(1,\n   2)"), "1 2");
242    }
243
244    #[test]
245    fn a_replacement_may_consume_tokens_that_follow_the_invocation() {
246        let mut pp = Pp::new();
247        pp.define("f(x) [x]");
248        pp.define("g f");
249        // `g` expands to `f`, and the parenthesis it needs is not in the replacement list, it
250        // is in the text after the invocation of `g`. This is the case that forces the
251        // pushback stream rather than expanding each macro into an isolated list.
252        assert_eq!(pp.text("g(1)"), "[1]");
253    }
254
255    #[test]
256    fn a_parenthesis_that_has_not_expanded_yet_does_not_count() {
257        let mut pp = Pp::new();
258        pp.define("lparen (");
259        pp.define("f(x) [x]");
260        pp.define("g f lparen 1 )");
261        // Rescanning reaches `f` while the next token is still `lparen`, so `f` is not an
262        // invocation and stays an identifier even though a parenthesis appears there a moment
263        // later. GCC and Clang agree, and getting this wrong is a way to expand things nobody
264        // asked for.
265        assert_eq!(pp.text("g"), "f ( 1 )");
266    }
267
268    #[test]
269    fn a_macro_does_not_expand_inside_its_own_expansion() {
270        let mut pp = Pp::new();
271        pp.define("A A + 1");
272        assert_eq!(pp.text("A"), "A + 1");
273    }
274
275    #[test]
276    fn mutually_recursive_macros_terminate_with_both_names_left() {
277        let mut pp = Pp::new();
278        pp.define("A B");
279        pp.define("B A");
280        assert_eq!(pp.text("A"), "A");
281        assert_eq!(pp.text("B"), "B");
282    }
283
284    #[test]
285    fn a_hidden_name_stays_hidden_when_it_is_carried_outwards() {
286        // The case a depth counter gets wrong: `x` is hidden inside `f`, and the result of
287        // `f` is then substituted into `g`, where a counter would have unwound and let it
288        // expand again.
289        let mut pp = Pp::new();
290        pp.define("f(x) x");
291        pp.define("g(y) [y]");
292        pp.define("h f(h)");
293        assert_eq!(pp.text("g(h)"), "[h]");
294    }
295
296    #[test]
297    fn stringify_puts_the_argument_in_quotes() {
298        let mut pp = Pp::new();
299        pp.define("str(x) #x");
300        assert_eq!(pp.text("str(hello)"), "\"hello\"");
301    }
302
303    #[test]
304    fn stringify_uses_the_unexpanded_argument() {
305        let mut pp = Pp::new();
306        pp.define("N 42");
307        pp.define("str(x) #x");
308        pp.define("xstr(x) str(x)");
309        assert_eq!(pp.text("str(N)"), "\"N\"");
310        assert_eq!(pp.text("xstr(N)"), "\"42\"", "one level of indirection expands first");
311    }
312
313    #[test]
314    fn stringify_collapses_whitespace_and_drops_it_at_the_edges() {
315        let mut pp = Pp::new();
316        pp.define("str(x) #x");
317        assert_eq!(pp.text("str(  a   +    b  )"), "\"a + b\"");
318    }
319
320    #[test]
321    fn stringify_escapes_quotes_and_backslashes_inside_literals() {
322        let mut pp = Pp::new();
323        pp.define("str(x) #x");
324        assert_eq!(pp.text(r#"str("a\n")"#), r#""\"a\\n\"""#);
325    }
326
327    #[test]
328    fn paste_joins_two_tokens_into_one() {
329        let mut pp = Pp::new();
330        pp.define("cat(a, b) a ## b");
331        assert_eq!(pp.text("cat(foo, bar)"), "foobar");
332        assert_eq!(pp.text("cat(1, 2)"), "12");
333        assert_eq!(pp.text("cat(+, =)"), "+=");
334    }
335
336    #[test]
337    fn paste_uses_the_unexpanded_arguments() {
338        let mut pp = Pp::new();
339        pp.define("N 42");
340        pp.define("cat(a, b) a ## b");
341        assert_eq!(pp.text("cat(N, N)"), "NN");
342    }
343
344    #[test]
345    fn the_result_of_a_paste_is_rescanned() {
346        let mut pp = Pp::new();
347        pp.define("foobar yes");
348        pp.define("cat(a, b) a ## b");
349        assert_eq!(pp.text("cat(foo, bar)"), "yes");
350    }
351
352    #[test]
353    fn pasting_an_empty_argument_leaves_the_other_side() {
354        let mut pp = Pp::new();
355        pp.define("cat(a, b) a ## b");
356        assert_eq!(pp.text("cat(foo,)"), "foo");
357        assert_eq!(pp.text("cat(, bar)"), "bar");
358        assert_eq!(pp.text("cat(,)"), "");
359    }
360
361    #[test]
362    fn a_paste_that_does_not_make_a_token_is_an_error_and_both_tokens_survive() {
363        let mut pp = Pp::new();
364        pp.define("cat(a, b) a ## b");
365        assert_eq!(pp.text("cat(+, foo)"), "+foo");
366        let errors = pp.errors();
367        assert_eq!(errors.len(), 1);
368        assert_eq!(errors[0].code, Some("E0313"));
369        assert!(errors[0].message.contains("pasting `+` and `foo`"));
370    }
371
372    /// The notes a diagnostic carries, as text, which is what a reader actually sees.
373    fn note_texts(d: &Diagnostic) -> Vec<&str> {
374        d.children.iter().map(|c| c.message.as_str()).collect()
375    }
376
377    #[test]
378    fn a_paste_error_says_which_macro_wrote_the_paste() {
379        // One macro deep. The note has to point at the `##` in the body rather than at the
380        // call, because the call is already where the error itself points.
381        let mut pp = Pp::new();
382        pp.define("cat(a, b) a ## b");
383        assert_eq!(pp.text("cat(+, foo)"), "+foo");
384        let errors = pp.errors();
385        let notes = note_texts(&errors[0]);
386        assert_eq!(notes[2], "expanded from macro `cat`");
387        // Offset 12 in the definition is the `##`, and the definition and the use are lexed
388        // from the same origin in these tests, so the number is readable as written.
389        assert_eq!(errors[0].children[2].span, Span::new(12, 14));
390    }
391
392    #[test]
393    fn a_paste_error_names_every_macro_it_came_out_of_outermost_first() {
394        // The case the trace exists for: the `##` is two macros away from the code the user
395        // wrote, and neither end of the chain on its own explains how it got there.
396        let mut pp = Pp::new();
397        pp.define("cat(a, b) a ## b");
398        pp.define("outer(y) cat(y, +)");
399        assert_eq!(pp.text("outer(z)"), "z+");
400        let errors = pp.errors();
401        assert_eq!(errors.len(), 1);
402        assert_eq!(errors[0].code, Some("E0313"));
403        let notes = note_texts(&errors[0]);
404        assert_eq!(&notes[2..], ["expanded from macro `outer`", "expanded from macro `cat`"]);
405        // `outer` is named at the `cat` inside its body, and `cat` at its own `##`.
406        assert_eq!(errors[0].children[2].span, Span::new(9, 12));
407        assert_eq!(errors[0].children[3].span, Span::new(12, 14));
408    }
409
410    #[test]
411    fn a_macro_called_wrongly_from_another_macro_says_where_it_was_called() {
412        let mut pp = Pp::new();
413        pp.define("two(a, b) a b");
414        pp.define("wrap(x) two(x)");
415        pp.text("wrap(1)");
416        let errors = pp.errors();
417        assert_eq!(errors.len(), 1);
418        assert_eq!(errors[0].code, Some("E0312"));
419        assert_eq!(note_texts(&errors[0])[1], "expanded from macro `wrap`");
420        assert_eq!(errors[0].children[1].span, Span::new(8, 11));
421    }
422
423    #[test]
424    fn a_macro_used_in_an_argument_is_not_blamed_on_the_macro_it_is_passed_to() {
425        // An argument is written by the caller, so a diagnostic from pre-expanding one is not
426        // inside the body of the macro being called and must not say that it is.
427        let mut pp = Pp::new();
428        pp.define("cat(a, b) a ## b");
429        pp.define("id(x) x");
430        pp.text("id(cat(+, foo))");
431        let errors = pp.errors();
432        assert_eq!(errors.len(), 1);
433        assert_eq!(note_texts(&errors[0])[2..], ["expanded from macro `cat`"]);
434    }
435
436    #[test]
437    fn variadic_arguments_arrive_as_one_argument_with_the_commas_intact() {
438        let mut pp = Pp::new();
439        pp.define("f(fmt, ...) g(fmt, __VA_ARGS__)");
440        assert_eq!(pp.text("f(\"%d %d\", 1, 2)"), "g(\"%d %d\", 1, 2)");
441    }
442
443    #[test]
444    fn the_gnu_named_variadic_form_works_the_same_way() {
445        let mut pp = Pp::new();
446        pp.define("f(fmt, rest...) g(fmt, rest)");
447        assert_eq!(pp.text("f(a, b, c)"), "g(a, b, c)");
448    }
449
450    #[test]
451    fn a_variadic_macro_may_be_called_with_nothing_for_the_variadic_part() {
452        let mut pp = Pp::new();
453        pp.define("f(a, ...) [a __VA_ARGS__]");
454        assert_eq!(pp.text("f(1)"), "[1 ]");
455    }
456
457    #[test]
458    fn the_gnu_comma_swallowing_extension_drops_the_comma() {
459        let mut pp = Pp::new();
460        pp.define("log(fmt, ...) printf(fmt, ## __VA_ARGS__)");
461        assert_eq!(pp.text("log(\"hi\")"), "printf(\"hi\")");
462        assert_eq!(pp.text("log(\"%d\", 1)"), "printf(\"%d\", 1)");
463    }
464
465    #[test]
466    fn va_opt_appears_only_when_there_are_variable_arguments() {
467        let mut pp = Pp::new();
468        pp.define("log(fmt, ...) printf(fmt __VA_OPT__(,) __VA_ARGS__)");
469        assert_eq!(pp.text("log(\"hi\")"), "printf(\"hi\" )");
470        assert_eq!(pp.text("log(\"%d\", 1)"), "printf(\"%d\" , 1)");
471    }
472
473    #[test]
474    fn va_opt_contents_are_substituted_like_any_other_replacement() {
475        let mut pp = Pp::new();
476        pp.define("f(a, ...) [a __VA_OPT__(and __VA_ARGS__ done)]");
477        assert_eq!(pp.text("f(1)"), "[1 ]");
478        assert_eq!(pp.text("f(1, 2)"), "[1 and 2 done]");
479    }
480
481    #[test]
482    fn va_opt_pastes_as_a_unit() {
483        let mut pp = Pp::new();
484        pp.define("f(a, ...) a ## __VA_OPT__(x)");
485        assert_eq!(pp.text("f(y)"), "y", "with no variable arguments it is a placemarker");
486        assert_eq!(pp.text("f(y, 1)"), "yx");
487    }
488
489    #[test]
490    fn too_few_arguments_are_reported_against_the_definition() {
491        let mut pp = Pp::new();
492        pp.define("f(a, b) a b");
493        assert_eq!(pp.text("f(1)"), "f", "the arguments are consumed, as GCC and Clang do");
494        let errors = pp.errors();
495        assert_eq!(errors.len(), 1);
496        assert_eq!(errors[0].code, Some("E0312"));
497        assert_eq!(errors[0].children.len(), 1, "the definition is worth pointing at");
498    }
499
500    #[test]
501    fn an_unterminated_argument_list_is_reported_at_the_parenthesis() {
502        let mut pp = Pp::new();
503        pp.define("f(a) a");
504        assert_eq!(pp.text("f(1"), "f");
505        let errors = pp.errors();
506        assert_eq!(errors[0].code, Some("E0311"));
507    }
508
509    #[test]
510    fn a_token_from_a_macro_is_reported_at_the_invocation() {
511        let mut pp = Pp::new();
512        pp.define("N 42");
513        let out = pp.expand("  N");
514        assert_eq!(out.len(), 1);
515        assert_eq!(out[0].expansion, out[0].report_span());
516        assert_eq!(out[0].expansion.lo, 2, "the invocation is at offset 2 in the input");
517        assert_ne!(out[0].span, out[0].expansion, "the spelling is in the macro body");
518    }
519
520    #[test]
521    fn hide_sets_are_shared_rather_than_rebuilt() {
522        let mut pp = Pp::new();
523        pp.define("A 1");
524        pp.define("B 2");
525        for _ in 0..100 {
526            pp.text("A B A B");
527        }
528        assert!(
529            pp.expander.hide_sets() <= 3,
530            "the empty set plus one per macro, however many times they are used"
531        );
532    }
533
534    #[test]
535    fn expansion_is_the_same_every_time() {
536        let mut first = Pp::new();
537        let mut second = Pp::new();
538        for pp in [&mut first, &mut second] {
539            pp.define("N 42");
540            pp.define("cat(a, b) a ## b");
541            pp.define("log(fmt, ...) printf(fmt, ## __VA_ARGS__)");
542        }
543        let src = "cat(x, y) N log(\"a\") log(\"b\", 1)";
544        assert_eq!(first.text(src), second.text(src));
545    }
546
547    /// The example from the standard, 6.10.4.5 in C23. It is the closest thing the
548    /// preprocessor has to a conformance test: every clause of the substitution rules shows
549    /// up in it and nothing about it is accidental. The expected text is the standard's own,
550    /// which GCC and Clang both reproduce.
551    #[test]
552    fn the_standards_rescanning_example() {
553        let mut pp = Pp::new();
554        pp.define("x 3");
555        pp.define("f(a) f(x * (a))");
556        pp.undef("x");
557        pp.define("x 2");
558        pp.define("g f");
559        pp.define("z z[0]");
560        pp.define("h g(~");
561        pp.define("m(a) a(w)");
562        pp.define("w 0,1");
563        pp.define("t(a) a");
564        pp.define("p() int");
565        pp.define("q(x) x");
566        pp.define("r(x,y) x ## y");
567        pp.define("str(x) # x");
568
569        assert_eq!(
570            pp.text("f(y+1) + f(f(z)) % t(t(g)(0) + t)(1);"),
571            "f(2 * (y+1)) + f(2 * (f(2 * (z[0])))) % f(2 * (0)) + t(1);"
572        );
573        // The standard writes `2+(3,4)` here with no space. Clang prints one, because its
574        // `-E` writer adds a separator after an expansion whether or not the tokens would
575        // run together. Both are conforming and the token sequence is the same either way.
576        assert_eq!(
577            pp.text("g(x+(3,4)-w) | h 5) & m\n(f)^m(m);"),
578            "f(2 * (2+(3,4)-0,1)) | f(2 * (~ 5)) & f(2 * (0,1))^m(0,1);"
579        );
580        assert_eq!(
581            pp.text("p() i[q()] = { q(1), r(2,3), r(4,), r(,5), r(,) };"),
582            "int i[] = { 1, 23, 4, 5, };"
583        );
584        assert_eq!(
585            pp.text("char c[2][6] = { str(hello), str() };"),
586            "char c[2][6] = { \"hello\", \"\" };"
587        );
588        assert!(pp.errors().is_empty(), "the standard's example is well formed");
589    }
590
591    /// The variadic example from the standard, 6.10.4.5 again.
592    #[test]
593    fn the_standards_variadic_example() {
594        let mut pp = Pp::new();
595        pp.define("debug(...) fprintf(stderr, __VA_ARGS__)");
596        pp.define("showlist(...) puts(#__VA_ARGS__)");
597        pp.define("report(test, ...) ((test)?puts(#test): printf(__VA_ARGS__))");
598
599        assert_eq!(pp.text("debug(\"Flag\");"), "fprintf(stderr, \"Flag\");");
600        assert_eq!(pp.text("debug(\"X = %d\\n\", x);"), "fprintf(stderr, \"X = %d\\n\", x);");
601        assert_eq!(
602            pp.text("showlist(The first, second, and third items.);"),
603            "puts(\"The first, second, and third items.\");"
604        );
605        assert_eq!(
606            pp.text("report(x>y, \"x is %d but y is %d\", x, y);"),
607            "((x>y)?puts(\"x>y\"): printf(\"x is %d but y is %d\", x, y));"
608        );
609        assert!(pp.errors().is_empty(), "the standard's example is well formed");
610    }
611
612    #[test]
613    fn milestone_is_recorded() {
614        assert!(MILESTONE.starts_with('M'));
615    }
616}