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() {
check_preprocessed_result("#define A B", "");
check_preprocessed_result("# /**/ \tdefine A B", "");
check_preprocessing_error("42 #define A B", PreprocessorError::UnexpectedHash);
check_preprocessing_error(
"# ; A B",
PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Semicolon)),
);
check_preprocessing_error("#CoucouLeChat", PreprocessorError::UnknownDirective);
}
#[test]
fn parse_error() {
check_preprocessing_error("#error", PreprocessorError::ErrorDirective);
}
#[test]
fn parse_define() {
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,
})),
);
check_preprocessing_error(
"#define
A",
PreprocessorError::UnexpectedNewLine,
);
check_preprocessing_error("#@", PreprocessorError::UnexpectedCharacter);
}
#[test]
fn parse_undef() {
check_preprocessing_error(
"#undef !",
PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Bang)),
);
check_preprocessing_error(
"#undef
A",
PreprocessorError::UnexpectedNewLine,
);
}
#[test]
fn argument_less_define() {
check_preprocessed_result(
"#define A B
A",
"B",
);
check_preprocessed_result(
"#define A
A something",
"something",
);
check_preprocessed_result(
"#define A A B C
A",
"A B C",
);
check_preprocessed_result(
"#define A foo
A()",
"foo()",
);
check_preprocessed_result(
"#define B C
#define A B
A",
"C",
);
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",
);
check_preprocessed_result(
"#define C A \\
B
C",
"A B",
);
check_preprocessed_result(
"#define C A /*
*/ B
C",
"A B",
);
check_preprocessed_result(
"#define B stuff
#define C stuffy stuff
#define A B C
#undef B
#undef C
#define C :)
A",
"B :)",
);
check_preprocessed_result(
"#define A ()
#define B\t(!
#define C/**/(a, b)
A B C",
"() (! (a, b)",
);
check_preprocessing_error("#define A #", PreprocessorError::UnexpectedHash);
}
#[test]
fn function_like_define() {
check_preprocessed_result(
"#define A(a) +a+
A(1)",
"+1+",
);
check_preprocessed_result(
"#define A() foo
A()",
"foo",
);
check_preprocessed_result(
"#define A(a, b) b a
A(1, 2)",
"2 1",
);
check_preprocessed_result(
"#define A(a) foobar
A + B",
"A + B",
);
check_preprocessing_error("#define A(a, a) foo", PreprocessorError::DuplicateParameter);
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)),
);
check_preprocessing_error(
"#define A(,a) foo",
PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Comma)),
);
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())),
);
check_preprocessing_error(
"#define A(a, b) foo
A(1, 2, 3)",
PreprocessorError::TooManyDefineArguments,
);
check_preprocessing_error(
"#define A(a, b) foo
A(1)",
PreprocessorError::TooFewDefineArguments,
);
check_preprocessed_result(
"#define A(a) foo a
A()",
"foo",
);
check_preprocessing_error(
"#define A(a, b) foo
A(",
PreprocessorError::UnexpectedEndOfInput,
);
check_preprocessing_error(
"#define A(a) foo
A($)",
PreprocessorError::UnexpectedCharacter,
);
check_preprocessing_error(
"#define A(a) foo
A(
#error
)",
PreprocessorError::ErrorDirective,
);
check_preprocessed_result(
"#define STUFF(a, b) a + b
STUFF((1, 2), 3)",
"(1, 2) + 3",
);
check_preprocessed_result(
"#define STUFF(a, b) a + b
STUFF((((()1, 2))), 3)",
"(((()1, 2))) + 3",
);
check_preprocessed_result(
"#define B(foo) (foo)
B(1 B(2))",
"(1 (2))",
);
check_preprocessed_result(
"#define A(x, y) x + y
#define COMMA ,
A(1 COMMA 2, 3)",
"1, 2 + 3",
);
check_preprocessed_result(
"#define A(x, y) x + y
#define COMMA ,
#define B(foo) A(foo)
B(1 COMMA 2)",
"1 + 2",
);
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))",
);
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)",
);
check_preprocessed_result(
"#define LPAREN (
#define COMMA ,
#define RPAREN )
#define A(a) a
#define B(a) a
#define C B(A(C))
C",
"C",
);
check_preprocessing_error(
"#define A(x, y) x + y
#define COMMA ,
#define B(foo) A(1, foo)
B(2 COMMA 3)",
PreprocessorError::TooManyDefineArguments,
);
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() {
check_preprocessed_result(
"#define A(x, y) (x + y)
#define A(x, y) (x + y)",
"",
);
check_preprocessed_result(
"#define A (x, y)
#define A (x, y)",
"",
);
check_preprocessing_error(
"#define A (a, y)
#define A (x, y)",
PreprocessorError::DefineRedefined,
);
check_preprocessing_error(
"#define A a b
#define A a",
PreprocessorError::DefineRedefined,
);
check_preprocessing_error(
"#define A a
#define A() a",
PreprocessorError::DefineRedefined,
);
check_preprocessing_error(
"#define A(b) a
#define A(c) a",
PreprocessorError::DefineRedefined,
);
check_preprocessing_error(
"#define A(b, d) a
#define A(c) a",
PreprocessorError::DefineRedefined,
);
check_preprocessing_error(
"#define A(b, d) a
#define A(d, b) a",
PreprocessorError::DefineRedefined,
);
}
#[test]
fn define_undef() {
check_preprocessed_result(
"#define A B
#undef A
A",
"A",
);
check_preprocessed_result(
"#undef A
A",
"A",
);
check_preprocessed_result(
"#define A B
#undef A
#undef A
A",
"A",
);
}
#[test]
fn parse_if() {
check_preprocessed_result(
"#if 0
1
#endif
#if 1
2
#endif",
"2",
);
check_preprocessed_result(
"#if 0
#if % ^ * )
#endif
#endif",
"",
);
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",
);
}
#[test]
fn parse_ifdef() {
check_preprocessed_result(
"#define A
#ifdef B
1
#endif
#ifdef A
2
#endif",
"2",
);
check_preprocessing_error(
"#ifdef B ;
#endif",
PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Semicolon)),
);
check_preprocessing_error(
"#ifdef
#endif",
PreprocessorError::UnexpectedNewLine,
);
check_preprocessed_result(
"#if 0
#ifdef B ;
#endif
#endif",
"",
);
check_preprocessed_result(
"#if 0
#ifdef
#endif
#endif",
"",
);
}
#[test]
fn parse_ifndef() {
check_preprocessed_result(
"#define A
#ifndef B
1
#endif
#ifndef A
2
#endif",
"1",
);
check_preprocessing_error(
"#ifndef B ;
#endif",
PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Semicolon)),
);
check_preprocessing_error(
"#ifndef
#endif",
PreprocessorError::UnexpectedNewLine,
);
check_preprocessed_result(
"#if 0
#ifndef B ;
#endif
#endif",
"",
);
check_preprocessed_result(
"#if 0
#ifndef
#endif
#endif",
"",
);
}
#[test]
fn parse_elif() {
check_preprocessed_result(
"#if 1
1
#elif 1
2
#endif
#if 0
3
#elif 1
4
#elif 0
5
#endif",
"1 4",
);
check_preprocessing_error(
"#elif
aaa",
PreprocessorError::ElifOutsideOfBlock,
);
check_preprocessing_error(
"#if 0
#else
#elif 1
#endif",
PreprocessorError::ElifAfterElse,
);
check_preprocessing_error(
"#if 0
#if 0
#else
#elif 1
#endif
#endif",
PreprocessorError::ElifAfterElse,
);
check_preprocessed_result(
"#if 0
#if 1
#elif # !
#endif
#endif",
"",
);
check_preprocessed_result(
"#if !defined(FOO)
#elif # !
#endif
#if 0
#elif 1
#elif # %
#endif",
"",
);
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() {
check_preprocessed_result(
"#if 0
1
#else
2
#endif
#if 0
3
#elif 0
4
#else
5
#endif",
"2 5",
);
check_preprocessing_error(
"#else
aaa",
PreprocessorError::ElseOutsideOfBlock,
);
check_preprocessing_error(
"#if 0
#else
#else
#endif",
PreprocessorError::MoreThanOneElse,
);
check_preprocessing_error(
"#if 0
#if 0
#else
#else
#endif
#endif",
PreprocessorError::MoreThanOneElse,
);
check_preprocessed_result(
"#if 1
A
#else
B
#endif
#if 0
C
#elif 1
D
#else
E
#endif",
"A D",
);
check_preprocessed_result(
"#if 0
#if 0
#else
FOO
#endif
#endif",
"",
);
}
#[test]
fn parse_endif() {
check_preprocessed_result(
"#if 0
a
#endif
b",
"b",
);
check_preprocessing_error(
"#if 1
#endif %",
PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Percent)),
);
check_preprocessing_error(
"#if 0
#endif %",
PreprocessorError::UnexpectedToken(TokenValue::Punct(Punct::Percent)),
);
check_preprocessed_result(
"#if 0
#if 0
#endif %
#endif",
"",
);
check_preprocessing_error("#if 0", PreprocessorError::UnfinishedBlock);
check_preprocessing_error(
"#if 1
#if 0
#endif",
PreprocessorError::UnfinishedBlock,
);
check_preprocessing_error("#endif", PreprocessorError::EndifOutsideOfBlock);
check_preprocessing_error(
"#if 1
#endif
#endif",
PreprocessorError::EndifOutsideOfBlock,
);
}
#[test]
fn skipping_behavior() {
check_preprocessed_result(
"#if 0
a b % 1 2u
#endif",
"",
);
check_preprocessed_result(
"#if 0
a # b
#endif",
"",
);
check_preprocessed_result(
"#if 0
#
#endif",
"",
);
check_preprocessed_result(
"#if 0
#CestlafauteaNandi
#endif",
"",
);
check_preprocessed_result(
"#if 0
#define A 1
#endif
#define A 2",
"",
);
check_preprocessed_result(
"#define A 1
#if 0
#undef A
#endif
A",
"1",
);
check_preprocessed_result(
"#if 0
#error
#endif",
"",
);
check_preprocessed_result(
"#if 0
#line 1000
#endif
__LINE__",
"4u",
);
check_preprocessed_result(
"#if 0
#version a b c
#extension 1 2 3
#pragma tic
#endif",
"",
);
}
#[test]
fn parse_line() {
check_preprocessed_result(
"#line 4u
#line 3
#line 0xF00
__LINE__",
"0xF01u",
);
check_preprocessing_error("#line !", PreprocessorError::UnexpectedEndOfInput);
check_preprocessing_error("#line", PreprocessorError::UnexpectedEndOfInput);
check_preprocessing_error(
"#line foo",
PreprocessorError::UnexpectedToken(TokenValue::Ident("foo".into())),
);
check_preprocessed_result(
"#if 0
#line !
#endif
__LINE__",
"4u",
);
check_preprocessing_error("#line 1 #", PreprocessorError::UnexpectedHash);
check_preprocessing_error("#line 4294967296u", PreprocessorError::LineOverflow);
check_preprocessed_result("#line 4294967295u", "");
check_preprocessing_error("#line -1", PreprocessorError::LineOverflow);
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",
);
check_preprocessed_result("#line 0 1", "");
check_preprocessing_error(
"#line 0 1 2",
PreprocessorError::UnexpectedToken(TokenValue::Integer(Integer {
value: 2,
signed: true,
width: 32,
})),
);
}
#[test]
fn line_define() {
check_preprocessed_result(
"__LINE__
__LINE__
__LINE__",
"1u 2u 4u",
);
check_preprocessed_result("__L\\\nINE__", "1u");
check_preprocessed_result(
"#define MY_DEFINE __LINE__
MY_DEFINE
MY\\\n_DEFINE",
"2u 3u",
);
check_preprocessed_result(
"#define A(foo) Bleh
A __LINE__ B",
"A 2u B",
);
check_preprocessed_result(
"#define B + __LINE__ +
#define A(X, Y) X __LINE__ Y B
A(-, -)
A(-, -
)",
"- 3u - + 3u +
- 5u - + 5u +",
);
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_preprocessed_result(
"#line 1000
__LINE__
__LINE__
#line 0
__LINE__
__LINE__",
"1001u 1002u 1u 2u",
);
check_preprocessed_result(
"#line 4294967294
__LINE__",
"4294967295u",
);
check_preprocessing_error(
"#line 4294967295
__LINE__",
PreprocessorError::LineOverflow,
);
check_preprocessing_error(
"#line 1.0",
PreprocessorError::UnexpectedToken(TokenValue::Float(Float {
value: 1.0,
width: 32,
})),
)
}
#[test]
fn parse_version() {
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!();
}
};
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!();
}
};
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!();
}
};
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() {
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!();
}
};
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() {
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() {
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!();
}
}
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!();
}
}
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!();
}
}
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!();
}
}
let mut pp = Preprocessor::new("A");
assert_eq!(
pp.add_define("A", "@").unwrap_err().0,
PreprocessorError::UnexpectedCharacter
);
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() {}
}