Skip to main content

rucc_lex/
convert.rs

1//! Phase 7: preprocessing tokens become the tokens the parser reads.
2//!
3//! Design: `spec/06-lexer-and-parser.md` section 6.1.
4//!
5//! This is the join between the preprocessor and the parser, and it is the last place where a
6//! spelling means anything. An identifier becomes a keyword when the dialect says that spelling
7//! is one, a preprocessing number becomes a typed value, a literal becomes elements, a run of
8//! adjacent string literals becomes one literal, and everything else is already what it is.
9//!
10//! # Where the values live
11//!
12//! A [`Token`] is sixteen bytes, the same budget a pp-token has and for the same reason: a
13//! large translation unit is tens of millions of them and the parser walks them more than once.
14//! A converted constant does not fit in sixteen bytes, so it does not live in the token. The
15//! token holds a small index and the values live in four vectors beside them, which is also the
16//! shape the parser wants, since it reaches for a constant's value at one node out of a hundred
17//! and reads the kind at every one.
18//!
19//! # Where the warnings come from
20//!
21//! The conversions report what a constant did through [`Remarks`] and never decide that any of
22//! it is a warning, because they do not hold the span. This is the layer that holds it, so this
23//! is where a remark becomes a diagnostic. Which remarks are warnings at all depends on
24//! `-pedantic`, and the split was measured on gcc 13.3 rather than guessed: a multi-character
25//! constant, an escape out of range, an overflowing floating constant and a decimal constant
26//! that came out unsigned are warnings with no flag at all, and the extensions, the escape gcc
27//! invented, the imaginary suffix and everything the dialect does not have yet are quiet until
28//! `-pedantic` asks.
29//!
30//! # What is not here
31//!
32//! Nothing turns a token back into text. `-E` prints pp-tokens, which is the stage before this
33//! one, so a spelling is never reconstructed from a converted value.
34
35use rucc_base::{Interner, Symbol};
36use rucc_diag::{Diagnostic, Span};
37use rucc_session::Std;
38use rucc_target::TargetInfo;
39
40use crate::keyword::{Keyword, Keywords};
41use crate::literal::{CharConstant, LiteralError, StringLiteral};
42use crate::number::{FloatConstant, IntConstant, IntError};
43use crate::remarks::Remarks;
44use crate::token::{PpToken, PpTokenKind, Punct, TokenFlags};
45
46/// What a token is.
47///
48/// Two bytes, so that a [`Token`] fits in sixteen. The categories that carry a value carry it
49/// in the token's `value` field instead of in the variant, which is what keeps it that small.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51pub enum TokenKind {
52    /// A keyword in this dialect.
53    Keyword(Keyword),
54    /// An identifier, whose symbol is in [`Token::value`].
55    Ident,
56    /// An integer constant, indexed by [`Token::value`] into [`Tokens::ints`].
57    Int,
58    /// A floating constant, indexed by [`Token::value`] into [`Tokens::floats`].
59    Float,
60    /// A character constant, indexed by [`Token::value`] into [`Tokens::chars`].
61    Char,
62    /// A string literal, indexed by [`Token::value`] into [`Tokens::strings`]. One token per run
63    /// of adjacent literals, because that is one literal.
64    Str,
65    /// A punctuator.
66    Punct(Punct),
67    /// End of the translation unit.
68    Eof,
69}
70
71/// One token, as the parser reads it.
72///
73/// Sixteen bytes, checked by a test below, and laid out the way [`PpToken`] is for the same
74/// reason.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct Token {
77    /// What it is.
78    pub kind: TokenKind,
79    /// Whether it started a line and whether anything came before it, carried through from the
80    /// pp-token so that a diagnostic can say "did you mean to write this on its own line".
81    pub flags: TokenFlags,
82    /// The symbol for an identifier, the index into the matching vector of [`Tokens`] for a
83    /// constant or a literal, and zero for everything else.
84    pub value: u32,
85    /// Where it came from, which for a run of string literals covers the whole run.
86    pub span: Span,
87}
88
89impl Token {
90    /// Whether this is the end of the translation unit.
91    #[inline]
92    #[must_use]
93    pub const fn is_eof(self) -> bool {
94        matches!(self.kind, TokenKind::Eof)
95    }
96
97    /// The keyword, when this is one.
98    #[inline]
99    #[must_use]
100    pub const fn keyword(self) -> Option<Keyword> {
101        match self.kind {
102            TokenKind::Keyword(word) => Some(word),
103            _ => None,
104        }
105    }
106
107    /// The punctuator, when this is one.
108    #[inline]
109    #[must_use]
110    pub const fn punct(self) -> Option<Punct> {
111        match self.kind {
112            TokenKind::Punct(punct) => Some(punct),
113            _ => None,
114        }
115    }
116
117    /// The identifier's symbol, when this is one.
118    #[inline]
119    #[must_use]
120    pub const fn ident(self) -> Option<Symbol> {
121        match self.kind {
122            TokenKind::Ident => Some(Symbol::from_raw(self.value)),
123            _ => None,
124        }
125    }
126}
127
128/// The tokens of a translation unit, with the values they refer to.
129#[derive(Debug, Default)]
130pub struct Tokens {
131    /// The tokens themselves, ending in one [`TokenKind::Eof`].
132    pub tokens: Vec<Token>,
133    /// The integer constants, in the order they were converted.
134    pub ints: Vec<IntConstant>,
135    /// The floating constants, in the order they were converted.
136    pub floats: Vec<FloatConstant>,
137    /// The character constants, in the order they were converted.
138    pub chars: Vec<CharConstant>,
139    /// The string literals, one per run of adjacent ones.
140    pub strings: Vec<StringLiteral>,
141}
142
143impl Tokens {
144    /// The integer constant `token` refers to, and [`None`] when it is not an integer constant.
145    #[must_use]
146    pub fn int(&self, token: Token) -> Option<&IntConstant> {
147        match token.kind {
148            TokenKind::Int => self.ints.get(token.value as usize),
149            _ => None,
150        }
151    }
152
153    /// The floating constant `token` refers to, and [`None`] when it is not one.
154    #[must_use]
155    pub fn float(&self, token: Token) -> Option<&FloatConstant> {
156        match token.kind {
157            TokenKind::Float => self.floats.get(token.value as usize),
158            _ => None,
159        }
160    }
161
162    /// The character constant `token` refers to, and [`None`] when it is not one.
163    #[must_use]
164    pub fn character(&self, token: Token) -> Option<&CharConstant> {
165        match token.kind {
166            TokenKind::Char => self.chars.get(token.value as usize),
167            _ => None,
168        }
169    }
170
171    /// The string literal `token` refers to, and [`None`] when it is not one.
172    #[must_use]
173    pub fn string(&self, token: Token) -> Option<&StringLiteral> {
174        match token.kind {
175            TokenKind::Str => self.strings.get(token.value as usize),
176            _ => None,
177        }
178    }
179}
180
181/// Everything phase 7 needs that is not the tokens.
182#[derive(Debug, Clone, Copy)]
183pub struct Convert<'a> {
184    /// The keyword table, built for this dialect before any source was read.
185    pub keywords: &'a Keywords,
186    /// Where the spellings are, since a pp-token carries a symbol rather than text.
187    pub interner: &'a Interner,
188    /// The target, which decides what a constant's type is and what a wide element is.
189    pub target: &'a TargetInfo,
190    /// The dialect, which decides what is a keyword and what earns a remark.
191    pub std: Std,
192    /// Whether `-pedantic` is on, which is the difference between a remark that is a warning
193    /// and one that is nothing at all.
194    pub pedantic: bool,
195}
196
197/// Converts a stream of preprocessing tokens into tokens.
198///
199/// The stream is what came out of macro expansion, so it has no directives left in it. A
200/// spelling that will not convert produces a diagnostic and a token that stands in for it, so
201/// that one bad constant does not cost the rest of the file its parse.
202#[must_use]
203pub fn convert(pp: &[PpToken], cx: &Convert<'_>) -> (Tokens, Vec<Diagnostic>) {
204    let mut out = Tokens { tokens: Vec::with_capacity(pp.len()), ..Tokens::default() };
205    let mut diagnostics = Vec::new();
206    let mut index = 0;
207    while index < pp.len() {
208        let token = pp[index];
209        index += 1;
210        match token.kind {
211            PpTokenKind::Ident => out.tokens.push(identifier(token, cx)),
212            PpTokenKind::Number => {
213                out.tokens.push(number(
214                    token,
215                    cx,
216                    &mut out.ints,
217                    &mut out.floats,
218                    &mut diagnostics,
219                ));
220            }
221            PpTokenKind::CharConst => {
222                out.tokens.push(char_const(token, cx, &mut out.chars, &mut diagnostics));
223            }
224            PpTokenKind::StringLit => {
225                // A run of adjacent literals is one literal, so the run is taken here rather
226                // than left for the parser, which would have to know the encoding rules to do
227                // it and would be the second place that knows them.
228                let start = index - 1;
229                while pp.get(index).is_some_and(|next| next.kind == PpTokenKind::StringLit) {
230                    index += 1;
231                }
232                let run = &pp[start..index];
233                out.tokens.push(string_lit(run, cx, &mut out.strings, &mut diagnostics));
234            }
235            PpTokenKind::Punct(punct) => out.tokens.push(Token {
236                kind: TokenKind::Punct(punct),
237                flags: token.flags,
238                value: 0,
239                span: token.span,
240            }),
241            PpTokenKind::Eof => out.tokens.push(Token {
242                kind: TokenKind::Eof,
243                flags: token.flags,
244                value: 0,
245                span: token.span,
246            }),
247            // A stray byte is a legal pp-token and never a token, and a header name cannot get
248            // here at all, because only a directive asks for one and no directive survives to
249            // this point. Both are reported and dropped, since there is nothing to stand in
250            // for either of them.
251            PpTokenKind::Other | PpTokenKind::HeaderName => {
252                let text = spelling(token, cx);
253                diagnostics
254                    .push(Diagnostic::error(format!("stray '{text}' in program"), token.span));
255            }
256        }
257    }
258    if out.tokens.last().is_none_or(|last| !last.is_eof()) {
259        // Every caller of this ends up indexing past the last real token, so the stream always
260        // ends in one of these even when the input did not.
261        let end =
262            out.tokens.last().map_or(Span::new(0, 0), |last| Span::new(last.span.hi, last.span.hi));
263        out.tokens.push(Token {
264            kind: TokenKind::Eof,
265            flags: TokenFlags::EMPTY,
266            value: 0,
267            span: end,
268        });
269    }
270    (out, diagnostics)
271}
272
273/// The spelling of a pp-token that has one.
274fn spelling<'a>(token: PpToken, cx: &Convert<'a>) -> &'a str {
275    token.value.map_or("", |symbol| cx.interner.resolve(symbol))
276}
277
278/// An identifier, which the dialect may have made a keyword.
279fn identifier(token: PpToken, cx: &Convert<'_>) -> Token {
280    let symbol = token.value.expect("an identifier carries its spelling");
281    let kind = match cx.keywords.get(symbol) {
282        Some(word) => TokenKind::Keyword(word),
283        None => TokenKind::Ident,
284    };
285    Token { kind, flags: token.flags, value: symbol.raw(), span: token.span }
286}
287
288/// A preprocessing number, which is an integer constant, a floating one, or neither.
289fn number(
290    token: PpToken,
291    cx: &Convert<'_>,
292    ints: &mut Vec<IntConstant>,
293    floats: &mut Vec<FloatConstant>,
294    diagnostics: &mut Vec<Diagnostic>,
295) -> Token {
296    let text = spelling(token, cx);
297    // Which of the two it is, is the integer path's answer rather than a guess made here: the
298    // grammars overlap at the front and only one of them can tell where the number stops.
299    match crate::number::integer(text, cx.std, cx.target) {
300        Ok(value) => {
301            report(value.remarks, None, token.span, cx, diagnostics);
302            ints.push(value);
303            let index = u32::try_from(ints.len() - 1).expect("that many constants in one file");
304            Token { kind: TokenKind::Int, flags: token.flags, value: index, span: token.span }
305        }
306        Err(IntError::Floating) => match crate::number::floating(text, cx.std, cx.target) {
307            Ok(value) => {
308                report(value.remarks, Some(value.ty.name()), token.span, cx, diagnostics);
309                floats.push(value);
310                let index =
311                    u32::try_from(floats.len() - 1).expect("that many constants in one file");
312                Token { kind: TokenKind::Float, flags: token.flags, value: index, span: token.span }
313            }
314            Err(error) => {
315                diagnostics.push(Diagnostic::error(error.message(), token.span));
316                // A zero of the right shape, so that the expression around it still parses and
317                // the user sees the one error they made rather than the ten it caused.
318                floats.push(zero_float(cx));
319                let index =
320                    u32::try_from(floats.len() - 1).expect("that many constants in one file");
321                Token { kind: TokenKind::Float, flags: token.flags, value: index, span: token.span }
322            }
323        },
324        Err(error) => {
325            diagnostics.push(Diagnostic::error(error.message(), token.span));
326            ints.push(IntConstant {
327                value: 0,
328                ty: crate::number::IntConstantType::Standard(rucc_types::IntKind::Int),
329                remarks: Remarks::NONE,
330            });
331            let index = u32::try_from(ints.len() - 1).expect("that many constants in one file");
332            Token { kind: TokenKind::Int, flags: token.flags, value: index, span: token.span }
333        }
334    }
335}
336
337/// The `0.0` that stands in for a floating constant that would not convert.
338fn zero_float(cx: &Convert<'_>) -> FloatConstant {
339    let ty = crate::number::FloatConstantType::Double;
340    FloatConstant {
341        value: rucc_base::float::Float::zero(ty.format(cx.target), false),
342        ty,
343        imaginary: false,
344        remarks: Remarks::NONE,
345    }
346}
347
348/// A character constant.
349fn char_const(
350    token: PpToken,
351    cx: &Convert<'_>,
352    chars: &mut Vec<CharConstant>,
353    diagnostics: &mut Vec<Diagnostic>,
354) -> Token {
355    let text = spelling(token, cx);
356    let value = match crate::literal::character(text, cx.std, cx.target) {
357        Ok(value) => {
358            report(value.remarks, None, token.span, cx, diagnostics);
359            value
360        }
361        Err(error) => {
362            diagnostics.push(Diagnostic::error(error.message(), token.span));
363            CharConstant {
364                value: 0,
365                encoding: crate::literal::Encoding::Plain,
366                remarks: Remarks::NONE,
367            }
368        }
369    };
370    chars.push(value);
371    let index = u32::try_from(chars.len() - 1).expect("that many constants in one file");
372    Token { kind: TokenKind::Char, flags: token.flags, value: index, span: token.span }
373}
374
375/// A run of adjacent string literals, which is one literal.
376fn string_lit(
377    run: &[PpToken],
378    cx: &Convert<'_>,
379    strings: &mut Vec<StringLiteral>,
380    diagnostics: &mut Vec<Diagnostic>,
381) -> Token {
382    let first = run[0];
383    let span = first.span.to(run[run.len() - 1].span);
384    let texts: Vec<&str> = run.iter().map(|token| spelling(*token, cx)).collect();
385    let value = match crate::literal::strings(&texts, cx.std, cx.target) {
386        Ok(value) => {
387            report(value.remarks, None, span, cx, diagnostics);
388            value
389        }
390        Err(error) => {
391            diagnostics.push(Diagnostic::error(error.message(), span));
392            // An empty literal of the encoding the run asked for, if it managed to agree on
393            // one, so that a `char *` initialised from it is still a `char *`.
394            let encoding = if error == LiteralError::MixedEncodings {
395                crate::literal::Encoding::Plain
396            } else {
397                crate::literal::Encoding::read_prefix(texts[0])
398            };
399            StringLiteral { elements: Vec::new(), encoding, remarks: Remarks::NONE }
400        }
401    };
402    strings.push(value);
403    let index = u32::try_from(strings.len() - 1).expect("that many literals in one file");
404    Token { kind: TokenKind::Str, flags: first.flags, value: index, span }
405}
406
407/// Turns the remarks a conversion made into the diagnostics this dialect wants.
408///
409/// `type_name` is the type a floating constant came out as, which the overflow wording names.
410/// The split between the warnings that need `-pedantic` and the ones that do not was measured
411/// on gcc 13.3 rather than guessed at.
412fn report(
413    remarks: Remarks,
414    type_name: Option<&str>,
415    span: Span,
416    cx: &Convert<'_>,
417    diagnostics: &mut Vec<Diagnostic>,
418) {
419    if remarks.is_none() {
420        return;
421    }
422
423    // On by default in gcc, because every one of these is a value that is not what the source
424    // looks like it says.
425    let always: [(Remarks, &str); 6] = [
426        (Remarks::MULTICHARACTER, "multi-character character constant"),
427        (Remarks::TOO_LONG, "character constant too long for its type"),
428        (Remarks::UNKNOWN_ESCAPE, "unknown escape sequence"),
429        (Remarks::HEX_ESCAPE_OUT_OF_RANGE, "hex escape sequence out of range"),
430        (Remarks::OCTAL_ESCAPE_OUT_OF_RANGE, "octal escape sequence out of range"),
431        (Remarks::UNSIGNED, "integer constant is so large that it is unsigned"),
432    ];
433    for (remark, message) in always {
434        if remarks.has(remark) {
435            diagnostics.push(Diagnostic::warning(message, span));
436        }
437    }
438    if remarks.has(Remarks::OUT_OF_RANGE) {
439        let ty = type_name.unwrap_or("double");
440        diagnostics
441            .push(Diagnostic::warning(format!("floating constant exceeds range of '{ty}'"), span));
442    }
443    if remarks.has(Remarks::TRUNCATED) {
444        diagnostics.push(Diagnostic::warning("floating constant truncated to zero", span));
445    }
446
447    if !cx.pedantic {
448        return;
449    }
450    // Quiet without `-pedantic`, because each of these is a value the compiler understood
451    // perfectly well and only the standard objects to.
452    let pedantic: [(Remarks, &str); 9] = [
453        (Remarks::NON_ISO_ESCAPE, "non-ISO-standard escape sequence"),
454        (Remarks::DOUBLE_SUFFIX, "suffix for double constant is a GCC extension"),
455        (Remarks::IMAGINARY, "imaginary constants are a GCC extension"),
456        (Remarks::BINARY, "binary constants are a C23 feature or GCC extension"),
457        (Remarks::EXTENDED_SUFFIX, "non-standard suffix on floating constant"),
458        (Remarks::HEX_FLOAT, "use of C99 hexadecimal floating constant"),
459        (Remarks::LONG_LONG, "use of C99 long long integer constant"),
460        (Remarks::SEPARATORS, "digit separators are a C23 feature"),
461        (Remarks::BIT_INT, "'_BitInt' constants are a C23 feature"),
462    ];
463    for (remark, message) in pedantic {
464        if remarks.has(remark) {
465            diagnostics.push(Diagnostic::warning(message, span));
466        }
467    }
468    if remarks.has(Remarks::UCN) {
469        diagnostics.push(Diagnostic::warning(
470            "universal character names are only valid in C++ and C99",
471            span,
472        ));
473    }
474}
475
476#[cfg(test)]
477mod tests {
478    use rucc_target::Triple;
479
480    use super::*;
481    use crate::lexer::{Options, tokenize};
482
483    /// Everything a conversion needs, built the way a driver would build it: the keyword table
484    /// first, before any source has been interned.
485    struct Fixture {
486        interner: Interner,
487        keywords: Keywords,
488        target: TargetInfo,
489        std: Std,
490        pedantic: bool,
491    }
492
493    impl Fixture {
494        fn new(std: Std) -> Fixture {
495            let mut interner = Interner::new();
496            let keywords = Keywords::new(&mut interner, std, true);
497            let target =
498                TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().expect("a triple"));
499            Fixture { interner, keywords, target, std, pedantic: false }
500        }
501
502        /// Lexes and converts `src`, which is what a translation unit with no directives does.
503        fn run(&mut self, src: &str) -> (Tokens, Vec<String>) {
504            let (pp, lex_diagnostics) =
505                tokenize(src.as_bytes(), 0, Options::new(), &mut self.interner);
506            assert!(lex_diagnostics.is_empty(), "the scanner disliked the source: {src}");
507            let cx = Convert {
508                keywords: &self.keywords,
509                interner: &self.interner,
510                target: &self.target,
511                std: self.std,
512                pedantic: self.pedantic,
513            };
514            let (tokens, diagnostics) = convert(&pp, &cx);
515            (tokens, diagnostics.iter().map(|d| d.message.clone()).collect())
516        }
517    }
518
519    fn kinds(src: &str) -> Vec<TokenKind> {
520        Fixture::new(Std::C23).run(src).0.tokens.iter().map(|t| t.kind).collect()
521    }
522
523    #[test]
524    fn a_token_is_sixteen_bytes() {
525        // The same budget a pp-token has, and for the same reason: a large translation unit
526        // holds tens of millions of these and the parser walks them more than once.
527        assert_eq!(size_of::<Token>(), 16);
528    }
529
530    #[test]
531    fn a_declaration_converts_into_keywords_an_identifier_and_a_constant() {
532        assert_eq!(
533            kinds("int x = 1;"),
534            vec![
535                TokenKind::Keyword(Keyword::Int),
536                TokenKind::Ident,
537                TokenKind::Punct(Punct::Eq),
538                TokenKind::Int,
539                TokenKind::Punct(Punct::Semi),
540                TokenKind::Eof,
541            ]
542        );
543    }
544
545    /// Which spellings are keywords is the dialect's business, and phase 7 is where it lands.
546    #[test]
547    fn the_dialect_decides_which_identifiers_are_keywords() {
548        let mut c89 = Fixture::new(Std::C89);
549        let (tokens, _) = c89.run("restrict");
550        assert_eq!(tokens.tokens[0].kind, TokenKind::Ident);
551        let mut c99 = Fixture::new(Std::C99);
552        let (tokens, _) = c99.run("restrict");
553        assert_eq!(tokens.tokens[0].kind, TokenKind::Keyword(Keyword::Restrict));
554    }
555
556    #[test]
557    fn a_number_becomes_whichever_kind_of_constant_it_is() {
558        let mut fixture = Fixture::new(Std::C23);
559        let (tokens, diagnostics) = fixture.run("1 2.5 0x1p3 1u");
560        assert!(diagnostics.is_empty());
561        let kinds: Vec<_> = tokens.tokens.iter().map(|t| t.kind).collect();
562        assert_eq!(
563            kinds,
564            vec![
565                TokenKind::Int,
566                TokenKind::Float,
567                TokenKind::Float,
568                TokenKind::Int,
569                TokenKind::Eof
570            ]
571        );
572        assert_eq!(tokens.int(tokens.tokens[0]).expect("an integer").value, 1);
573        assert!(tokens.float(tokens.tokens[1]).is_some());
574        // The index is into the vector for that kind, so the second float is the second entry
575        // and the second integer is not.
576        assert_eq!(tokens.tokens[2].value, 1);
577        assert_eq!(tokens.tokens[3].value, 1);
578        assert_eq!(tokens.int(tokens.tokens[3]).expect("an integer").value, 1);
579        // Asking the wrong kind for its value gets nothing rather than the wrong constant.
580        assert!(tokens.float(tokens.tokens[0]).is_none());
581        assert!(tokens.string(tokens.tokens[0]).is_none());
582    }
583
584    /// A run of adjacent literals is one token, because it is one object, and the span covers
585    /// all of it so that a diagnostic underlines the whole thing.
586    #[test]
587    fn adjacent_string_literals_become_one_token() {
588        let mut fixture = Fixture::new(Std::C23);
589        let (tokens, diagnostics) = fixture.run(r#"char *s = "a" "b" L"c";"#);
590        assert!(diagnostics.is_empty(), "{diagnostics:?}");
591        let literal = tokens
592            .tokens
593            .iter()
594            .find(|t| t.kind == TokenKind::Str)
595            .copied()
596            .expect("a string literal");
597        let value = tokens.string(literal).expect("the literal");
598        assert_eq!(value.elements, vec![0x61, 0x62, 0x63]);
599        assert_eq!(value.encoding, crate::literal::Encoding::Wide);
600        assert_eq!(tokens.tokens.iter().filter(|t| t.kind == TokenKind::Str).count(), 1);
601        // The span runs from the first quote to the last.
602        assert_eq!(literal.span.lo, 10);
603        assert_eq!(literal.span.hi, 22);
604    }
605
606    #[test]
607    fn a_character_constant_carries_its_value_and_its_warning() {
608        let mut fixture = Fixture::new(Std::C23);
609        let (tokens, diagnostics) = fixture.run("'ab'");
610        assert_eq!(diagnostics, vec!["multi-character character constant".to_owned()]);
611        assert_eq!(tokens.character(tokens.tokens[0]).expect("a constant").value, 0x6162);
612    }
613
614    /// Measured on gcc 13.3: these are warnings with no flag at all, because each one is a
615    /// value that is not what the source looks like it says.
616    #[test]
617    fn the_warnings_that_need_no_flag_are_given_without_one() {
618        let mut fixture = Fixture::new(Std::C17);
619        let (_, diagnostics) = fixture.run(r"'abcde' '\q' '\x1ff' '\400' 1e400 1e-400");
620        assert_eq!(
621            diagnostics,
622            vec![
623                "character constant too long for its type".to_owned(),
624                "unknown escape sequence".to_owned(),
625                "hex escape sequence out of range".to_owned(),
626                "octal escape sequence out of range".to_owned(),
627                "floating constant exceeds range of 'double'".to_owned(),
628                "floating constant truncated to zero".to_owned(),
629            ]
630        );
631    }
632
633    /// And these are quiet until `-pedantic` asks, which was measured the same way.
634    #[test]
635    fn the_warnings_that_need_pedantic_wait_for_it() {
636        let mut quiet = Fixture::new(Std::C17);
637        let (_, diagnostics) = quiet.run(r"1.0d 1.0i 0b1010 '\e'");
638        assert!(diagnostics.is_empty(), "{diagnostics:?}");
639
640        let mut loud = Fixture::new(Std::C17);
641        loud.pedantic = true;
642        let (_, diagnostics) = loud.run(r"1.0d 1.0i 0b1010 '\e'");
643        assert_eq!(
644            diagnostics,
645            vec![
646                "suffix for double constant is a GCC extension".to_owned(),
647                "imaginary constants are a GCC extension".to_owned(),
648                "binary constants are a C23 feature or GCC extension".to_owned(),
649                "non-ISO-standard escape sequence".to_owned(),
650            ]
651        );
652    }
653
654    #[test]
655    fn the_overflow_warning_names_the_type_the_constant_actually_has() {
656        let mut fixture = Fixture::new(Std::C23);
657        let (_, diagnostics) = fixture.run("1e400f");
658        assert_eq!(diagnostics, vec!["floating constant exceeds range of 'float'".to_owned()]);
659    }
660
661    /// One bad constant costs one diagnostic and nothing else, because the token it stands in
662    /// for is still there and the rest of the declaration still parses.
663    #[test]
664    fn a_constant_that_will_not_convert_still_leaves_a_token_behind() {
665        let mut fixture = Fixture::new(Std::C23);
666        let (tokens, diagnostics) = fixture.run("int x = 1.2.3;");
667        assert_eq!(diagnostics.len(), 1);
668        let kinds: Vec<_> = tokens.tokens.iter().map(|t| t.kind).collect();
669        assert_eq!(
670            kinds,
671            vec![
672                TokenKind::Keyword(Keyword::Int),
673                TokenKind::Ident,
674                TokenKind::Punct(Punct::Eq),
675                TokenKind::Float,
676                TokenKind::Punct(Punct::Semi),
677                TokenKind::Eof,
678            ]
679        );
680
681        let mut fixture = Fixture::new(Std::C23);
682        let (tokens, diagnostics) = fixture.run("int x = 42ux;");
683        assert_eq!(diagnostics, vec!["invalid suffix on integer constant".to_owned()]);
684        assert_eq!(tokens.int(tokens.tokens[3]).expect("a stand in").value, 0);
685    }
686
687    #[test]
688    fn a_run_of_literals_with_two_prefixes_is_refused_the_way_gcc_refuses_it() {
689        let mut fixture = Fixture::new(Std::C23);
690        let (tokens, diagnostics) = fixture.run(r#"u"a" L"b""#);
691        assert_eq!(
692            diagnostics,
693            vec!["unsupported non-standard concatenation of string literals".to_owned()]
694        );
695        assert!(tokens.string(tokens.tokens[0]).expect("a stand in").elements.is_empty());
696    }
697
698    /// A stray byte is a legal pp-token, so the scanner passes it through and this is the layer
699    /// that has to say no.
700    #[test]
701    fn a_stray_byte_is_an_error_here_and_nowhere_earlier() {
702        let mut fixture = Fixture::new(Std::C23);
703        let (tokens, diagnostics) = fixture.run("a ` b");
704        assert_eq!(diagnostics, vec!["stray '`' in program".to_owned()]);
705        let kinds: Vec<_> = tokens.tokens.iter().map(|t| t.kind).collect();
706        assert_eq!(kinds, vec![TokenKind::Ident, TokenKind::Ident, TokenKind::Eof]);
707    }
708
709    #[test]
710    fn the_stream_always_ends_in_end_of_file() {
711        let mut fixture = Fixture::new(Std::C23);
712        let (tokens, _) = fixture.run("");
713        assert_eq!(tokens.tokens.len(), 1);
714        assert!(tokens.tokens[0].is_eof());
715        // Even when the input had none, which is what a caller building a stream by hand does.
716        let (tokens, _) = convert(
717            &[],
718            &Convert {
719                keywords: &fixture.keywords,
720                interner: &fixture.interner,
721                target: &fixture.target,
722                std: fixture.std,
723                pedantic: false,
724            },
725        );
726        assert_eq!(tokens.tokens.len(), 1);
727        assert!(tokens.tokens[0].is_eof());
728    }
729
730    #[test]
731    fn a_token_says_what_it_is_without_the_caller_matching_on_the_kind() {
732        let mut fixture = Fixture::new(Std::C23);
733        let (tokens, _) = fixture.run("int x;");
734        assert_eq!(tokens.tokens[0].keyword(), Some(Keyword::Int));
735        assert_eq!(tokens.tokens[0].ident(), None);
736        assert!(tokens.tokens[1].ident().is_some());
737        assert_eq!(tokens.tokens[2].punct(), Some(Punct::Semi));
738        assert_eq!(tokens.tokens[2].keyword(), None);
739    }
740
741    /// The flags survive the conversion, because a diagnostic about a token that should have
742    /// been on its own line needs to know that it was not.
743    #[test]
744    fn the_flags_come_through_from_the_preprocessing_token() {
745        let mut fixture = Fixture::new(Std::C23);
746        let (tokens, _) = fixture.run("a\n b");
747        assert!(tokens.tokens[0].flags.has(TokenFlags::START_OF_LINE));
748        assert!(tokens.tokens[1].flags.has(TokenFlags::START_OF_LINE));
749        assert!(tokens.tokens[1].flags.has(TokenFlags::LEADING_SPACE));
750    }
751}