pp-rs 0.2.1

Shader preprocessor
Documentation
use super::lexer::{self, Token as LexerToken, TokenValue as LexerTokenValue};
use super::pp::{convert_lexer_token, Preprocessor, PreprocessorItem};
use super::token::{Float, Integer, Location, PreprocessorError, Punct, Token, TokenValue};

struct NoopPreprocessor<'a> {
    lexer: lexer::Lexer<'a>,
}

impl<'a> NoopPreprocessor<'a> {
    pub fn new(input: &'a str) -> NoopPreprocessor {
        NoopPreprocessor {
            lexer: lexer::Lexer::new(input),
        }
    }
}

impl<'a> Iterator for NoopPreprocessor<'a> {
    type Item = Result<Token, (PreprocessorError, Location)>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            match self.lexer.next() {
                Some(Ok(LexerToken {
                    value: LexerTokenValue::NewLine,
                    ..
                })) => continue,
                Some(Ok(token)) => return Some(Ok(convert_lexer_token(token).unwrap())),
                None => return None,
                Some(Err(err)) => return Some(Err(err)),
            }
        }
    }
}

#[track_caller]
fn check_preprocessed_result(input: &str, expected: &str) {
    let pp_items: Vec<PreprocessorItem> = Preprocessor::new(input).collect();
    let noop_items: Vec<PreprocessorItem> = NoopPreprocessor::new(expected).collect();

    assert_eq!(pp_items.len(), noop_items.len());
    for (pp_item, noop_item) in pp_items.iter().zip(noop_items) {
        if let (Ok(pp_tok), Ok(ref noop_tok)) = (pp_item, noop_item) {
            assert_eq!(pp_tok.value, noop_tok.value);
        } else {
            unreachable!();
        }
    }
}

#[track_caller]
fn check_preprocessing_error(input: &str, expected_err: PreprocessorError) {
    for item in Preprocessor::new(input) {
        if let Err((err, _)) = item {
            assert_eq!(err, expected_err);
            return;
        }
    }
    unreachable!();
}

#[test]
fn parse_directive() {
    // Test parsing a simple directive
    check_preprocessed_result("#define A B", "");

    // Test parsing a simple directive with comment right after the hash
    check_preprocessed_result("# /**/ \tdefine A B", "");

    // Test preprocessing directive can only come after a newline
    check_preprocessing_error("42 #define A B", PreprocessorError::UnexpectedHash);

    // Test not an identifier after the hash
    check_preprocessing_error(
        "# ; A B",
        PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Semicolon)),
    );

    // Test a fake directive
    check_preprocessing_error("#CoucouLeChat", PreprocessorError::UnknownDirective);
}

#[test]
fn parse_error() {
    // Test preprocessing directive can only come after a newline
    check_preprocessing_error("#error", PreprocessorError::ErrorDirective);
}

#[test]
fn parse_define() {
    // Test the define name must be an identifier
    check_preprocessing_error(
        "#define [",
        PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::LeftBracket)),
    );
    check_preprocessing_error(
        "#define 1.0",
        PreprocessorError::UnexpectedToken(TokenValue::Float(Float {
            value: 1.0,
            width: 32,
        })),
    );

    // Test that there must be a name before the new line
    check_preprocessing_error(
        "#define
        A",
        PreprocessorError::UnexpectedNewLine,
    );

    // Test putting a garbage character for the define name
    check_preprocessing_error("#@", PreprocessorError::UnexpectedCharacter);
}

#[test]
fn parse_undef() {
    // Test the define name must be an identifier
    check_preprocessing_error(
        "#undef !",
        PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Bang)),
    );

    // Test that there must be a name before the new line
    check_preprocessing_error(
        "#undef
        A",
        PreprocessorError::UnexpectedNewLine,
    );
}

#[test]
fn argument_less_define() {
    // Test a simple case
    check_preprocessed_result(
        "#define A B
         A",
        "B",
    );

    // Test an empty define
    check_preprocessed_result(
        "#define A
         A something",
        "something",
    );

    // Test a define containing a token that's itself
    check_preprocessed_result(
        "#define A A B C
         A",
        "A B C",
    );

    // Test a define invocation followed by () doesn't remove them
    check_preprocessed_result(
        "#define A foo
         A()",
        "foo()",
    );

    // Test nesting define
    check_preprocessed_result(
        "#define B C
         #define A B
         A",
        "C",
    );

    // Test nesting with a bunch of empty tokens
    check_preprocessed_result(
        "#define D
         #define C D D
         #define B C C D D
         #define A B B C C D D
         A truc",
        "truc",
    );

    // Test line continuation doesn't break the define
    check_preprocessed_result(
        "#define C A \\
         B
         C",
        "A B",
    );

    // Test that multiline comments don't break the define
    check_preprocessed_result(
        "#define C A /*
         */ B
         C",
        "A B",
    );

    // Test that substitution in defines happens at invocation time
    // Checks both when undefined a ident found in A and redefining it.
    check_preprocessed_result(
        "#define B stuff
         #define C stuffy stuff
         #define A B C
         #undef B
         #undef C
         #define C :)
         A",
        "B :)",
    );

    // The first token of the define can be a ( if it has whitespace after
    // the identifier
    check_preprocessed_result(
        "#define A ()
         #define B\t(!
         #define C/**/(a, b)
         A B C",
        "() (! (a, b)",
    );

    // Check that hashes are disallowed in defines
    check_preprocessing_error("#define A #", PreprocessorError::UnexpectedHash);
}

#[test]
fn function_like_define() {
    // Test calling a define with 1 argument
    check_preprocessed_result(
        "#define A(a) +a+
         A(1)",
        "+1+",
    );

    // Test calling a define with 0 arguments
    check_preprocessed_result(
        "#define A() foo
         A()",
        "foo",
    );

    // Test called a define with multiple arguments
    check_preprocessed_result(
        "#define A(a, b) b a
         A(1, 2)",
        "2 1",
    );

    // Test not calling a function-like macro just returns the identifier
    check_preprocessed_result(
        "#define A(a) foobar
         A + B",
        "A + B",
    );

    // Test that duplicate argument names are disallowed
    check_preprocessing_error("#define A(a, a) foo", PreprocessorError::DuplicateParameter);

    // Test that non ident or , are disallowed in the parameter list
    check_preprocessing_error(
        "#define A(%) foo",
        PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Percent)),
    );
    check_preprocessing_error(
        "#define A(a, %) foo",
        PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Percent)),
    );

    // Test that starting the param list with a comma is disallowed
    check_preprocessing_error(
        "#define A(,a) foo",
        PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Comma)),
    );

    // Test that two identifier in a row is disallowed
    check_preprocessing_error(
        "#define A(a b) foo",
        PreprocessorError::UnexpectedToken(TokenValue::Ident("b".to_string())),
    );
    check_preprocessing_error(
        "#define A(a, b c) foo",
        PreprocessorError::UnexpectedToken(TokenValue::Ident("c".to_string())),
    );

    // Test passing too many arguments is disallowed
    check_preprocessing_error(
        "#define A(a, b) foo
         A(1, 2, 3)",
        PreprocessorError::TooManyDefineArguments,
    );

    // Test passing too few arguments is disallowed
    check_preprocessing_error(
        "#define A(a, b) foo
         A(1)",
        PreprocessorError::TooFewDefineArguments,
    );

    // Test passing no argument to a define with one parameter.
    check_preprocessed_result(
        "#define A(a) foo a
         A()",
        "foo",
    );

    // Test EOF while parsing define arguments
    check_preprocessing_error(
        "#define A(a, b) foo
         A(",
        PreprocessorError::UnexpectedEndOfInput,
    );

    // Test unknown token while parsing define arguments
    check_preprocessing_error(
        "#define A(a) foo
         A($)",
        PreprocessorError::UnexpectedCharacter,
    );

    // Test #error while parsing arguments
    check_preprocessing_error(
        "#define A(a) foo
         A(
         #error
         )",
        PreprocessorError::ErrorDirective,
    );

    // Test that commas inside () are not used to split parameters
    check_preprocessed_result(
        "#define STUFF(a, b) a + b
         STUFF((1, 2), 3)",
        "(1, 2) + 3",
    );

    // Test that commas inside more nesting
    check_preprocessed_result(
        "#define STUFF(a, b) a + b
         STUFF((((()1, 2))), 3)",
        "(((()1, 2))) + 3",
    );

    // Test that a macro can be used in its own arguments
    check_preprocessed_result(
        "#define B(foo) (foo)
         B(1 B(2))",
        "(1 (2))",
    );

    // Test that define expansion doesn't happen while parsing define call arguments. If it
    // were, the COMMA would make a third argument to A appear, which would be an error.
    check_preprocessed_result(
        "#define A(x, y) x + y
         #define COMMA ,
         A(1 COMMA 2, 3)",
        "1, 2 + 3",
    );

    // Test that define expansion of arguments happens before giving tokens in the define
    // call. If it weren't the COMMA wouldn't make a second argument to A appear, which would
    // be an error.
    check_preprocessed_result(
        "#define A(x, y) x + y
         #define COMMA ,
         #define B(foo) A(foo)
         B(1 COMMA 2)",
        "1 + 2",
    );

    // Same but checking args are fully expanded by doing some weird nesting. The inner B's
    // call to A will create the token that will cause the outer B to call A with two arguments
    check_preprocessed_result(
        "#define A(a, b) ,(a + b)
         #define B(foo) A(foo)
         #define COMMA ,
         B(1 B(2 COMMA 3))",
        ",(1 + (2 + 3))",
    );

    // Test that the ( , and ) can come from the expansion of an argument.
    check_preprocessed_result(
        "#define LPAREN (
         #define COMMA ,
         #define RPAREN )
         #define A(a, b) (a + b)
         #define B(a) a
         B(A LPAREN 1 COMMA 2 RPAREN)",
        "(1 + 2)",
    );

    // Test that a define being expanded cannot be re-expanded inside an argument to an inner
    // define call.
    check_preprocessed_result(
        "#define LPAREN (
         #define COMMA ,
         #define RPAREN )
         #define A(a) a
         #define B(a) a
         #define C B(A(C))
         C",
        "C",
    );

    // Test that an error during define argument expansion gets surfaced properly.
    check_preprocessing_error(
        "#define A(x, y) x + y
         #define COMMA ,
         #define B(foo) A(1, foo)
         B(2 COMMA 3)",
        PreprocessorError::TooManyDefineArguments,
    );

    // Test putting comments inside the arguments to a function-like macro
    check_preprocessed_result(
        "#define A(x, y) x + y
         A(/*this is a 2*/ 2,
           // And below is a 3!
           3
        )",
        "2 + 3",
    );
}

#[test]
fn define_redefinition() {
    // Test that it is valid to redefine a define with the same tokens.
    // Function-like case.
    check_preprocessed_result(
        "#define A(x, y) (x + y)
         #define A(x, y) (x + y)",
        "",
    );
    // Not function-like case.
    check_preprocessed_result(
        "#define A (x, y)
         #define A (x, y)",
        "",
    );

    // Oh no a token is different!
    check_preprocessing_error(
        "#define A (a, y)
         #define A (x, y)",
        PreprocessorError::DefineRedefined,
    );

    // Oh no, one has more tokens!
    check_preprocessing_error(
        "#define A a b
         #define A a",
        PreprocessorError::DefineRedefined,
    );

    // Oh no, one is function-like and not the other
    check_preprocessing_error(
        "#define A a
         #define A() a",
        PreprocessorError::DefineRedefined,
    );

    // Oh no, a parameter name is different!
    check_preprocessing_error(
        "#define A(b) a
         #define A(c) a",
        PreprocessorError::DefineRedefined,
    );

    // Oh no, the parameter count is different!
    check_preprocessing_error(
        "#define A(b, d) a
         #define A(c) a",
        PreprocessorError::DefineRedefined,
    );

    // Oh no, the parameter order is different!
    check_preprocessing_error(
        "#define A(b, d) a
         #define A(d, b) a",
        PreprocessorError::DefineRedefined,
    );
}

#[test]
fn define_undef() {
    // Basic test
    check_preprocessed_result(
        "#define A B
         #undef A
         A",
        "A",
    );

    // It is ok to undef a non-existent define
    check_preprocessed_result(
        "#undef A
         A",
        "A",
    );

    // It is ok to undef a define twice
    check_preprocessed_result(
        "#define A B
         #undef A
         #undef A
         A",
        "A",
    );
}

#[test]
fn parse_if() {
    // Basic test of parsing and operations.
    check_preprocessed_result(
        "#if 0
             1
         #endif
         #if 1
             2
         #endif",
        "2",
    );

    // Check that garbage tokens are allowed if we are skipping.
    check_preprocessed_result(
        "#if 0
         #if % ^ * )
         #endif
         #endif",
        "",
    );

    // Check a couple simple expressions
    check_preprocessed_result(
        "#define FOO
         #if defined(FOO)
         A
         #endif",
        "A",
    );
    check_preprocessed_result(
        "#if defined FOO
         A
         #endif",
        "",
    );
    check_preprocessed_result(
        "#define FOO 0
         #if FOO
         A
         #endif",
        "",
    );

    check_preprocessed_result(
        "#define FOO FOO
         #if FOO
         #endif",
        "",
    );

    check_preprocessed_result(
        "#define FOO 1
         #define BAR 1
         #if (FOO & BAR) == 1
         2
         #endif",
        "2",
    );

    check_preprocessed_result(
        "#if 1 + -2 * 3 == -5
         2
         #endif",
        "2",
    );

    check_preprocessed_result(
        "#if 4 % 3 == 1
         2
         #endif",
        "2",
    );

    check_preprocessed_result(
        "#if 4 / 3 == 1
         2
         #endif",
        "2",
    );

    // TODO test that the if_parse gives back the unused tokens
    // TODO test expressions?
}

#[test]
fn parse_ifdef() {
    // Basic test of parsing and operations.
    check_preprocessed_result(
        "#define A
         #ifdef B
             1
         #endif
         #ifdef A
             2
         #endif",
        "2",
    );

    // Check that extra tokens after the identifier are disallowed.
    check_preprocessing_error(
        "#ifdef B ;
         #endif",
        PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Semicolon)),
    );

    // Check that the identifier is required.
    check_preprocessing_error(
        "#ifdef
         #endif",
        PreprocessorError::UnexpectedNewLine,
    );

    // Check that extra tokens are allowed if we are skipping.
    check_preprocessed_result(
        "#if 0
         #ifdef B ;
         #endif
         #endif",
        "",
    );

    // Check that having no identifier is allowed if we are skipping.
    check_preprocessed_result(
        "#if 0
         #ifdef
         #endif
         #endif",
        "",
    );
}

#[test]
fn parse_ifndef() {
    // Basic test of parsing and operations.
    check_preprocessed_result(
        "#define A
         #ifndef B
             1
         #endif
         #ifndef A
             2
         #endif",
        "1",
    );

    // Check that extra tokens after the identifier are disallowed.
    check_preprocessing_error(
        "#ifndef B ;
         #endif",
        PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Semicolon)),
    );

    // Check that the identifier is required.
    check_preprocessing_error(
        "#ifndef
         #endif",
        PreprocessorError::UnexpectedNewLine,
    );

    // Check that extra tokens are allowed if we are skipping.
    check_preprocessed_result(
        "#if 0
         #ifndef B ;
         #endif
         #endif",
        "",
    );

    // Check that having no identifier is allowed if we are skipping.
    check_preprocessed_result(
        "#if 0
         #ifndef
         #endif
         #endif",
        "",
    );
}

#[test]
fn parse_elif() {
    // Basic test of using elif
    check_preprocessed_result(
        "#if 1
             1
         #elif 1
             2
         #endif
         #if 0
             3
         #elif 1
             4
         #elif 0
             5
         #endif",
        "1 4",
    );

    // Check that #elif must appear in a block
    check_preprocessing_error(
        "#elif
         aaa",
        PreprocessorError::ElifOutsideOfBlock,
    );

    // Check that #elif must appear before the #else
    check_preprocessing_error(
        "#if 0
         #else
         #elif 1
         #endif",
        PreprocessorError::ElifAfterElse,
    );

    // Check that #elif must appear before the #else even when skipping
    check_preprocessing_error(
        "#if 0
             #if 0
             #else
             #elif 1
             #endif
         #endif",
        PreprocessorError::ElifAfterElse,
    );

    // Check that the condition isn't processed if the block is skipped.
    check_preprocessed_result(
        "#if 0
            #if 1
            #elif # !
            #endif
         #endif",
        "",
    );

    // Check that the condition isn't processed if a previous segment was used.
    check_preprocessed_result(
        "#if !defined(FOO)
         #elif # !
         #endif

         #if 0
         #elif 1
         #elif # %
         #endif",
        "",
    );

    // Check that elif isn't taken if at least one block was taken.
    check_preprocessed_result(
        "#if 1
             A
         #elif 1
             B
         #endif
         
         #if 0
             C
         #elif 1
             D
         #elif 1
             E
         #endif",
        "A D",
    );
}

#[test]
fn parse_else() {
    // Basic test of using else with if and elif
    check_preprocessed_result(
        "#if 0
             1
         #else
             2
         #endif
         #if 0
             3
         #elif 0
             4
         #else
             5
         #endif",
        "2 5",
    );

    // Check that #else must appear in a block
    check_preprocessing_error(
        "#else
         aaa",
        PreprocessorError::ElseOutsideOfBlock,
    );

    // Check that else can only appear once in a block, when skipping or not.
    check_preprocessing_error(
        "#if 0
         #else
         #else
         #endif",
        PreprocessorError::MoreThanOneElse,
    );
    check_preprocessing_error(
        "#if 0
            #if 0
            #else
            #else
            #endif
         #endif",
        PreprocessorError::MoreThanOneElse,
    );

    // Check that else isn't taken if at least one block was taken.
    check_preprocessed_result(
        "#if 1
             A
         #else
             B
         #endif
         #if 0
             C
         #elif 1
             D
         #else
             E
         #endif",
        "A D",
    );

    // Check that else isn't taken if it's skipped from the outside.
    check_preprocessed_result(
        "#if 0
             #if 0
             #else
                FOO
             #endif
         #endif",
        "",
    );
}

#[test]
fn parse_endif() {
    // Basic test of using endif
    check_preprocessed_result(
        "#if 0
         a
         #endif
         b",
        "b",
    );

    // Check that extra tokens are disallowed.
    check_preprocessing_error(
        "#if 1
         #endif %",
        PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Percent)),
    );

    // Check that extra tokens are disallowed even for an inner_skipped block.
    check_preprocessing_error(
        "#if 0
         #endif %",
        PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Percent)),
    );

    // Check that extra tokens are allowed if we are skipping.
    check_preprocessed_result(
        "#if 0
         #if 0
         #endif %
         #endif",
        "",
    );

    // Check that a missing endif triggers an error, nest and unnested case.
    check_preprocessing_error("#if 0", PreprocessorError::UnfinishedBlock);
    check_preprocessing_error(
        "#if 1
         #if 0
         #endif",
        PreprocessorError::UnfinishedBlock,
    );

    // Check that endif must be well nested with ifs.
    check_preprocessing_error("#endif", PreprocessorError::EndifOutsideOfBlock);
    check_preprocessing_error(
        "#if 1
         #endif
         #endif",
        PreprocessorError::EndifOutsideOfBlock,
    );
}

#[test]
fn skipping_behavior() {
    // Check regular tokens are skipped
    check_preprocessed_result(
        "#if 0
             a b % 1 2u
         #endif",
        "",
    );
    // Check random hash is allowed while skipping
    check_preprocessed_result(
        "#if 0
             a # b
         #endif",
        "",
    );

    // Check that a hash at the start of the line and nothing else if valid while skipping
    check_preprocessed_result(
        "#if 0
             #
         #endif",
        "",
    );

    // Check invalid directives are allowed while skipping
    check_preprocessed_result(
        "#if 0
             #CestlafauteaNandi
         #endif",
        "",
    );

    // Check that defines skipped (otherwise there would be a redefinition error)
    check_preprocessed_result(
        "#if 0
             #define A 1
         #endif
         #define A 2",
        "",
    );

    // Check that undefs are skipped.
    check_preprocessed_result(
        "#define A 1
         #if 0
             #undef A
         #endif
         A",
        "1",
    );

    // Check that #error is skipped.
    check_preprocessed_result(
        "#if 0
             #error
         #endif",
        "",
    );

    // Check that #line directives are skipped.
    check_preprocessed_result(
        "#if 0
             #line 1000
         #endif
         __LINE__",
        "4u",
    );

    // Check that #version/extension/pragma are skipped.
    check_preprocessed_result(
        "#if 0
             #version a b c
             #extension 1 2 3
             #pragma tic
         #endif",
        "",
    );
}

#[test]
fn parse_line() {
    // Test that #line with a uint and int is allowed.
    check_preprocessed_result(
        "#line 4u
         #line 3
         #line 0xF00
         __LINE__",
        "0xF01u",
    );

    // Test with something other than a number after #line (including a newline)
    check_preprocessing_error("#line !", PreprocessorError::UnexpectedEndOfInput);
    check_preprocessing_error("#line", PreprocessorError::UnexpectedEndOfInput);
    check_preprocessing_error(
        "#line foo",
        PreprocessorError::UnexpectedToken(TokenValue::Ident("foo".into())),
    );

    // Test that an invalid #line directive is allowed if skipping
    check_preprocessed_result(
        "#if 0
         #line !
         #endif
         __LINE__",
        "4u",
    );
    // Test that #line must have a newline after the integer (this will change when #line
    // supports constant expressions)
    check_preprocessing_error("#line 1 #", PreprocessorError::UnexpectedHash);

    // test that the integer must fit in a u32
    check_preprocessing_error("#line 4294967296u", PreprocessorError::LineOverflow);
    // test that the integer must fit in a u32
    check_preprocessed_result("#line 4294967295u", "");
    check_preprocessing_error("#line -1", PreprocessorError::LineOverflow);

    // Test some expression
    check_preprocessed_result(
        "#line 20 << 2 + 1
         __LINE__",
        "161u",
    );
    check_preprocessed_result(
        "#line 20 * 0 -2 + 100
        __LINE__",
        "99u",
    );
    check_preprocessed_result(
        "#line 0 (1 << 1 * (10)) % 2
        __LINE__",
        "1u",
    );

    // Test passing both a line and file id is allowed.
    check_preprocessed_result("#line 0 1", "");

    // Test passing more numbers is invalid.
    check_preprocessing_error(
        "#line 0 1 2",
        PreprocessorError::UnexpectedToken(TokenValue::Integer(Integer {
            value: 2,
            signed: true,
            width: 32,
        })),
    );
}

#[test]
fn line_define() {
    // Test that the __LINE__ define gives the number of the line.
    check_preprocessed_result(
        "__LINE__
         __LINE__

         __LINE__",
        "1u 2u 4u",
    );

    // Test that __LINE__ split over multiple lines gives the first line.
    check_preprocessed_result("__L\\\nINE__", "1u");

    // Test that the __LINE__ define used in define gives the invocation's line
    check_preprocessed_result(
        "#define MY_DEFINE __LINE__
         MY_DEFINE
         MY\\\n_DEFINE",
        "2u 3u",
    );

    // Test a corner case where the __LINE__ is a peeked token for function-like
    // define parsing.
    check_preprocessed_result(
        "#define A(foo) Bleh
         A __LINE__ B",
        "A 2u B",
    );

    // Test that __LINE__ inside function like defines is the position of the closing )
    check_preprocessed_result(
        "#define B + __LINE__ +
         #define A(X, Y) X __LINE__ Y B
         A(-, -)
         A(-, -
         )",
        "- 3u - + 3u +
         - 5u - + 5u +",
    );

    // Test that the __LINE__ inside a define's argument get the correct value.
    check_preprocessed_result(
        "#define A(X) X
         A(__LINE__
         __LINE__)",
        "2u 3u",
    );
    check_preprocessed_result(
        "#define B(X) X
         #define A(X) B(X) + __LINE__
         A(__LINE__)",
        "3u + 3u",
    );

    // Check that #line is taken into account and can modify the line number in both directions.
    check_preprocessed_result(
        "#line 1000
         __LINE__
         __LINE__

         #line 0
         __LINE__
         __LINE__",
        "1001u 1002u 1u 2u",
    );

    // Check that line computations are not allowed to overflow an u32
    check_preprocessed_result(
        "#line 4294967294
         __LINE__",
        "4294967295u",
    );
    check_preprocessing_error(
        "#line 4294967295
         __LINE__",
        PreprocessorError::LineOverflow,
    );

    // Check that line directives don't allow floats
    check_preprocessing_error(
        "#line 1.0",
        PreprocessorError::UnexpectedToken(TokenValue::Float(Float {
            value: 1.0,
            width: 32,
        })),
    )
}

#[test]
fn parse_version() {
    // Check that the #version directive is recognized and gets all the tokens until the newline
    let tokens: Vec<PreprocessorItem> = Preprocessor::new("#version 1 ; (").collect();
    assert_eq!(tokens.len(), 1);
    match &tokens[0] {
        Ok(Token {
            value: TokenValue::Version(version),
            ..
        }) => {
            assert!(!version.has_comments_before);
            assert!(version.is_first_directive);
            assert_eq!(version.tokens.len(), 3);
            assert_eq!(
                version.tokens[0].value,
                TokenValue::Integer(Integer {
                    value: 1,
                    signed: true,
                    width: 32
                })
            );
            assert_eq!(version.tokens[1].value, TokenValue::Punct(Punct::Semicolon));
            assert_eq!(version.tokens[2].value, TokenValue::Punct(Punct::LeftParen));
        }
        _ => {
            unreachable!();
        }
    };

    // Check that we correctly detect comments before the #version directive.
    let tokens: Vec<PreprocessorItem> = Preprocessor::new("/**/#version (").collect();
    assert_eq!(tokens.len(), 1);
    match &tokens[0] {
        Ok(Token {
            value: TokenValue::Version(version),
            ..
        }) => {
            assert!(version.has_comments_before);
            assert!(version.is_first_directive);
            assert_eq!(version.tokens.len(), 1);
            assert_eq!(version.tokens[0].value, TokenValue::Punct(Punct::LeftParen));
        }
        _ => {
            unreachable!();
        }
    };

    // Check that we properly detect tokens before the #version directive.
    let tokens: Vec<PreprocessorItem> = Preprocessor::new("4 \n #version (").collect();
    assert_eq!(tokens.len(), 2);
    match &tokens[1] {
        Ok(Token {
            value: TokenValue::Version(version),
            ..
        }) => {
            assert!(!version.has_comments_before);
            assert!(!version.is_first_directive);
            assert_eq!(version.tokens.len(), 1);
            assert_eq!(version.tokens[0].value, TokenValue::Punct(Punct::LeftParen));
        }
        _ => {
            unreachable!();
        }
    };

    // Same thing but with another preprocessor directive.
    let tokens: Vec<PreprocessorItem> = Preprocessor::new("#line 1\n #version (").collect();
    assert_eq!(tokens.len(), 1);
    match &tokens[0] {
        Ok(Token {
            value: TokenValue::Version(version),
            ..
        }) => {
            assert!(!version.has_comments_before);
            assert!(!version.is_first_directive);
            assert_eq!(version.tokens.len(), 1);
            assert_eq!(version.tokens[0].value, TokenValue::Punct(Punct::LeftParen));
        }
        _ => {
            unreachable!();
        }
    };
}

#[test]
fn parse_extension() {
    // Check that the #extension directive is recognized and gets all the tokens until the newline
    let tokens: Vec<PreprocessorItem> =
        Preprocessor::new("#extension USE_WEBGPU_INSTEAD_OF_GL : required").collect();
    assert_eq!(tokens.len(), 1);
    match &tokens[0] {
        Ok(Token {
            value: TokenValue::Extension(extension),
            ..
        }) => {
            assert!(!extension.has_non_directive_before);
            assert_eq!(extension.tokens.len(), 3);
            assert_eq!(
                extension.tokens[0].value,
                TokenValue::Ident("USE_WEBGPU_INSTEAD_OF_GL".into())
            );
            assert_eq!(extension.tokens[1].value, TokenValue::Punct(Punct::Colon));
            assert_eq!(
                extension.tokens[2].value,
                TokenValue::Ident("required".into())
            );
        }
        _ => {
            unreachable!();
        }
    };

    // Check that we correctly detect non directive tokens before the #extension
    let tokens: Vec<PreprocessorItem> = Preprocessor::new("miaou\n#extension").collect();
    assert_eq!(tokens.len(), 2);
    match &tokens[1] {
        Ok(Token {
            value: TokenValue::Extension(extension),
            ..
        }) => {
            assert!(extension.has_non_directive_before);
            assert_eq!(extension.tokens.len(), 0);
        }
        _ => {
            unreachable!();
        }
    };
}

#[test]
fn parse_pragma() {
    // Check that the #extension directive is recognized and gets all the tokens until the newline
    let tokens: Vec<PreprocessorItem> = Preprocessor::new("#pragma stuff").collect();
    assert_eq!(tokens.len(), 1);
    match &tokens[0] {
        Ok(Token {
            value: TokenValue::Pragma(pragma),
            ..
        }) => {
            assert_eq!(pragma.tokens.len(), 1);
            assert_eq!(pragma.tokens[0].value, TokenValue::Ident("stuff".into()));
        }
        _ => {
            unreachable!();
        }
    };
}

#[test]
fn add_define() {
    // Test adding multiple defines at the start.
    let mut pp = Preprocessor::new("A B");
    pp.add_define("A", "bat").unwrap();
    pp.add_define("B", "man").unwrap();

    match pp.next() {
        Some(Ok(Token {
            value: TokenValue::Ident(ident),
            ..
        })) => {
            assert_eq!(ident, "bat");
        }
        _ => {
            unreachable!();
        }
    }
    match pp.next() {
        Some(Ok(Token {
            value: TokenValue::Ident(ident),
            ..
        })) => {
            assert_eq!(ident, "man");
        }
        _ => {
            unreachable!();
        }
    }

    // Test that a multiline define works.
    let mut pp = Preprocessor::new("A");
    pp.add_define("A", "bat\nman").unwrap();

    match pp.next() {
        Some(Ok(Token {
            value: TokenValue::Ident(ident),
            ..
        })) => {
            assert_eq!(ident, "bat");
        }
        _ => {
            unreachable!();
        }
    }
    match pp.next() {
        Some(Ok(Token {
            value: TokenValue::Ident(ident),
            ..
        })) => {
            assert_eq!(ident, "man");
        }
        _ => {
            unreachable!();
        }
    }

    // Test adding defines in the middle without overwriting.
    let mut pp = Preprocessor::new("A A");
    match pp.next() {
        Some(Ok(Token {
            value: TokenValue::Ident(ident),
            ..
        })) => {
            assert_eq!(ident, "A");
        }
        _ => {
            unreachable!();
        }
    }

    pp.add_define("A", "foo").unwrap();
    match pp.next() {
        Some(Ok(Token {
            value: TokenValue::Ident(ident),
            ..
        })) => {
            assert_eq!(ident, "foo");
        }
        _ => {
            unreachable!();
        }
    }

    // Test adding defines in the middle with overwriting.
    let mut pp = Preprocessor::new(
        "#define A bat
         A A",
    );
    match pp.next() {
        Some(Ok(Token {
            value: TokenValue::Ident(ident),
            ..
        })) => {
            assert_eq!(ident, "bat");
        }
        _ => {
            unreachable!();
        }
    }

    pp.add_define("A", "foo").unwrap();
    match pp.next() {
        Some(Ok(Token {
            value: TokenValue::Ident(ident),
            ..
        })) => {
            assert_eq!(ident, "foo");
        }
        _ => {
            unreachable!();
        }
    }

    // Test a parsing error.
    let mut pp = Preprocessor::new("A");
    assert_eq!(
        pp.add_define("A", "@").unwrap_err().0,
        PreprocessorError::UnexpectedCharacter
    );

    // Test some crashes detected by fuzzing
    let mut pp = Preprocessor::new("#ifG/fp");
    while let Some(_) = pp.next() {}

    let mut pp = Preprocessor::new("\n#if~O%t");
    while let Some(_) = pp.next() {}

    let mut pp = Preprocessor::new("#if~f>>~f");
    while let Some(_) = pp.next() {}
}