Files
another-boids-in-rust/vendor/pp-rs/src/pp_tests.rs

1414 lines
34 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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("#if G/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() {}
}