pp-rs 0.2.1

Shader preprocessor
Documentation
use super::lexer::{
    CharsAndLine, Lexer, LexerItem, ReplaceComments, SkipBackslashNewline, Token, TokenValue,
    COMMENT_SENTINEL_VALUE,
};
use super::token::{Float, Integer, Location, PreprocessorError, Punct};
use std::ops::Range;

fn c(c: char, line: u32) -> Option<(char, u32)> {
    Some((c, line))
}

fn l(line: u32, pos: Range<u32>) -> Location {
    Location {
        line,
        start: pos.start,
        end: pos.end,
    }
}

fn unwrap_token(item: Option<LexerItem>) -> Token {
    item.unwrap().unwrap()
}

fn unwrap_token_value(item: Option<LexerItem>) -> TokenValue {
    unwrap_token(item).value
}

fn unwrap_error(item: Option<LexerItem>) -> PreprocessorError {
    item.unwrap().unwrap_err().0
}

fn expect_lexer_end(lexer: &mut Lexer) {
    assert_eq!(unwrap_token_value(lexer.next()), TokenValue::NewLine);
    assert_eq!(lexer.next(), None);
}

impl From<i32> for TokenValue {
    fn from(value: i32) -> Self {
        TokenValue::Integer(Integer {
            value: value as u64,
            signed: true,
            width: 32,
        })
    }
}

impl From<u32> for TokenValue {
    fn from(value: u32) -> Self {
        TokenValue::Integer(Integer {
            value: value as u64,
            signed: false,
            width: 32,
        })
    }
}

impl From<f32> for TokenValue {
    fn from(value: f32) -> Self {
        TokenValue::Float(Float { value, width: 32 })
    }
}

#[test]
fn chars_and_location() {
    // Test handling of characters in a line.
    let mut it = CharsAndLine::new("abc");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c('b', 1));
    assert_eq!(it.next(), c('c', 1));
    assert_eq!(it.next(), None);

    // Test handling of \n in the regular case.
    let mut it = CharsAndLine::new("a\nb");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c('\n', 1));
    assert_eq!(it.next(), c('b', 2));
    assert_eq!(it.next(), None);

    // Test handling of \r in the regular case.
    let mut it = CharsAndLine::new("a\rb");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c('\n', 1));
    assert_eq!(it.next(), c('b', 2));
    assert_eq!(it.next(), None);

    // Test handling of \n\r.
    let mut it = CharsAndLine::new("a\n\rb");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c('\n', 1));
    assert_eq!(it.next(), c('b', 2));
    assert_eq!(it.next(), None);

    // Test handling of \r\n.
    let mut it = CharsAndLine::new("a\r\nb");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c('\n', 1));
    assert_eq!(it.next(), c('b', 2));
    assert_eq!(it.next(), None);

    // Test handling of a mix of \r and \n
    let mut it = CharsAndLine::new("\n\r\n\r\r\r\n");
    assert_eq!(it.next(), c('\n', 1));
    assert_eq!(it.next(), c('\n', 2));
    assert_eq!(it.next(), c('\n', 3));
    assert_eq!(it.next(), c('\n', 4));
    assert_eq!(it.next(), None);

    // Unicode handling
    let mut it = CharsAndLine::new("a→üs🦀");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c('→', 1));
    assert_eq!(it.next(), c('ü', 1));
    assert_eq!(it.next(), c('s', 1));
    assert_eq!(it.next(), c('🦀', 1));
    assert_eq!(it.next(), None);
}

#[test]
fn skip_backslash_newline() {
    // Test a simple case.
    let mut it = SkipBackslashNewline::new("a\\\nb");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c('b', 2));
    assert_eq!(it.next(), None);

    // Test a double case that requires the loop in the algorithm.
    let mut it = SkipBackslashNewline::new("a\\\n\\\nb");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c('b', 3));
    assert_eq!(it.next(), None);

    // Test a backslash on its own
    let mut it = SkipBackslashNewline::new("a\\b");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c('\\', 1));
    assert_eq!(it.next(), c('b', 1));
    assert_eq!(it.next(), None);

    // Test a case just before EOF
    let mut it = SkipBackslashNewline::new("\\\n");
    assert_eq!(it.next(), None);
}

#[test]
fn replace_comments() {
    // Test a slash that's not a comment
    let mut it = ReplaceComments::new("a/b");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c('/', 1));
    assert_eq!(it.next(), c('b', 1));
    assert_eq!(it.next(), None);

    // Test a slash with nothing afterwards
    let mut it = ReplaceComments::new("a/");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c('/', 1));
    assert_eq!(it.next(), None);

    // Test a single-line comment
    let mut it = ReplaceComments::new("a//foo\nb");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c(COMMENT_SENTINEL_VALUE, 1));
    assert_eq!(it.next(), c('\n', 1));
    assert_eq!(it.next(), c('b', 2));
    assert_eq!(it.next(), None);

    // Test a single-line comment without an ending newline
    let mut it = ReplaceComments::new("//foo");
    assert_eq!(it.next(), c(COMMENT_SENTINEL_VALUE, 1));
    assert_eq!(it.next(), None);

    // Test a single-line comment without nothing afterwards
    let mut it = ReplaceComments::new("//");
    assert_eq!(it.next(), c(COMMENT_SENTINEL_VALUE, 1));
    assert_eq!(it.next(), None);

    // Test a single-line comment with a line continuation
    let mut it = ReplaceComments::new("//foo\\\na");
    assert_eq!(it.next(), c(COMMENT_SENTINEL_VALUE, 1));
    assert_eq!(it.next(), None);

    // Test a multi-line comment
    let mut it = ReplaceComments::new("a/*fo\n\no*/b");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c(COMMENT_SENTINEL_VALUE, 1));
    assert_eq!(it.next(), c('b', 3));
    assert_eq!(it.next(), None);

    // Test a multi-line comment, without a proper ending (only the *)
    let mut it = ReplaceComments::new("a/*fo\n\no*");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c(COMMENT_SENTINEL_VALUE, 1));
    assert_eq!(it.next(), None);

    // Test a multi-line comment, without a proper ending (nothing)
    let mut it = ReplaceComments::new("a/*fo\n\no");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c(COMMENT_SENTINEL_VALUE, 1));
    assert_eq!(it.next(), None);

    // Test a multi-line comment, or /*/ not being a complete one
    let mut it = ReplaceComments::new("a/*/b");
    assert_eq!(it.next(), c('a', 1));
    assert_eq!(it.next(), c(COMMENT_SENTINEL_VALUE, 1));
    assert_eq!(it.next(), None);
}

#[test]
fn lex_whitespace() {
    // Empty input gives nothing.
    let mut it = Lexer::new("");
    assert_eq!(it.next(), None);

    // Pure whitespace give nothing too
    let mut it = Lexer::new("/**/\t //a");
    assert_eq!(it.next(), None);
}

#[test]
fn lex_newline() {
    let mut it = Lexer::new("\r\n\n");
    assert_eq!(unwrap_token_value(it.next()), TokenValue::NewLine);
    assert_eq!(unwrap_token_value(it.next()), TokenValue::NewLine);
    assert_eq!(it.next(), None);

    // Check a newline is added only if the last token wasn't a newline
    let mut it = Lexer::new("\r\n\n\t/**/ //");
    assert_eq!(unwrap_token_value(it.next()), TokenValue::NewLine);
    expect_lexer_end(&mut it);

    let mut it = Lexer::new("\r\n\n#");
    assert_eq!(unwrap_token_value(it.next()), TokenValue::NewLine);
    assert_eq!(unwrap_token_value(it.next()), TokenValue::NewLine);
    assert_eq!(unwrap_token_value(it.next()), TokenValue::Hash);
    assert_eq!(unwrap_token_value(it.next()), TokenValue::NewLine);
    assert_eq!(it.next(), None);
}

#[test]
fn lex_hash() {
    let mut it = Lexer::new("a#b");
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("a".to_string())
    );
    let token = unwrap_token(it.next());
    assert_eq!(token.value, TokenValue::Hash);
    assert_eq!(token.location, l(1, 1..2));
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("b".to_string())
    );
    expect_lexer_end(&mut it);

    let mut it = Lexer::new("\nvoid #");
    assert_eq!(unwrap_token_value(it.next()), TokenValue::NewLine);
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("void".into())
    );
    let token = unwrap_token(it.next());
    assert_eq!(token.value, TokenValue::Hash);
    assert_eq!(token.location, l(2, 6..7));
    expect_lexer_end(&mut it);
}

#[test]
fn lex_metadata() {
    // Test the metadata of the first token
    let mut it = Lexer::new("1");
    assert_eq!(
        unwrap_token(it.next()),
        Token {
            value: 1.into(),
            location: l(1, 0..1),
            leading_whitespace: true,
            start_of_line: true
        }
    );
    expect_lexer_end(&mut it);

    // Test various whitespaces are recognized (or lack of)
    let mut it = Lexer::new(" 1/*\n*/2\t3+\n4");
    // 1 is the first token and the whitespace doesn't prevent it from being the start of the
    // line
    assert_eq!(
        unwrap_token(it.next()),
        Token {
            value: 1.into(),
            location: l(1, 1..2),
            leading_whitespace: true,
            start_of_line: true
        }
    );
    // 2 is not at the start of the line because the \n in the /**/ doesn't count, however its
    // location correctly lists the second line.
    assert_eq!(
        unwrap_token(it.next()),
        Token {
            value: 2.into(),
            location: l(2, 7..8),
            leading_whitespace: true,
            start_of_line: false
        }
    );
    assert_eq!(
        unwrap_token(it.next()),
        Token {
            value: 3.into(),
            location: l(2, 9..10),
            leading_whitespace: true,
            start_of_line: false
        }
    );
    // + doesn't have a leading whitespace
    assert_eq!(
        unwrap_token(it.next()),
        Token {
            value: Punct::Plus.into(),
            location: l(2, 10..11),
            leading_whitespace: false,
            start_of_line: false
        }
    );
    // The newline is correctly tagged on the preceeding line
    assert_eq!(
        unwrap_token(it.next()),
        Token {
            value: TokenValue::NewLine,
            location: l(2, 11..12),
            leading_whitespace: false,
            start_of_line: false
        }
    );
    // 4 is after a newline that correctly sets start_of_line
    assert_eq!(
        unwrap_token(it.next()),
        Token {
            value: 4.into(),
            location: l(3, 12..13),
            leading_whitespace: true,
            start_of_line: true
        }
    );
    // The final newline added by the lexer is at the correct position
    assert_eq!(
        unwrap_token(it.next()),
        Token {
            value: TokenValue::NewLine,
            location: l(3, 13..13),
            leading_whitespace: false,
            start_of_line: false
        }
    );
    assert_eq!(it.next(), None);
}

#[test]
fn lex_identifiers() {
    // Test some basic identifier cases
    let mut it = Lexer::new("foo BA_R baz0");
    let token = unwrap_token(it.next());
    assert_eq!(token.value, TokenValue::Ident("foo".to_string()));
    assert_eq!(token.location, l(1, 0..3),);
    let token = unwrap_token(it.next());
    assert_eq!(token.value, TokenValue::Ident("BA_R".to_string()));
    assert_eq!(token.location, l(1, 4..8),);
    let token = unwrap_token(it.next());
    assert_eq!(token.value, TokenValue::Ident("baz0".to_string()));
    assert_eq!(token.location, l(1, 9..13),);
    expect_lexer_end(&mut it);

    // Test _ is a valid identifier
    let mut it = Lexer::new("_");
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("_".to_string())
    );
    expect_lexer_end(&mut it);

    // Test that identifiers are not split by escaped newlines
    let mut it = Lexer::new("a\\\nb");
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("ab".to_string())
    );
    expect_lexer_end(&mut it);

    // Test that identifiers are split by other whitespace like /**/
    let mut it = Lexer::new("a/**/b");
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("a".to_string())
    );
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("b".to_string())
    );
    expect_lexer_end(&mut it);
}

#[test]
fn lex_decimal() {
    // Test some basic cases
    let mut it = Lexer::new("1 0u 42 65536U");
    assert_eq!(unwrap_token_value(it.next()), 1.into());
    let token = unwrap_token(it.next());
    assert_eq!(token.value, 0u32.into());
    assert_eq!(token.location, l(1, 2..4),);
    let token = unwrap_token(it.next());
    assert_eq!(token.value, 42.into());
    assert_eq!(token.location, l(1, 5..7),);
    let token = unwrap_token(it.next());
    assert_eq!(token.value, 65536u32.into());
    assert_eq!(token.location, l(1, 8..14),);
    expect_lexer_end(&mut it);

    // Test splitting with identifiers
    let mut it = Lexer::new("31ab");
    assert_eq!(unwrap_token_value(it.next()), 31.into());
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("ab".to_string())
    );
    expect_lexer_end(&mut it);

    // Test splitting with whitespace
    let mut it = Lexer::new("31/**/32");
    assert_eq!(unwrap_token_value(it.next()), 31.into());
    assert_eq!(unwrap_token_value(it.next()), 32.into());
    expect_lexer_end(&mut it);

    // Test splitting with punctuation
    let mut it = Lexer::new("31+32");
    assert_eq!(unwrap_token_value(it.next()), 31.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::Plus.into());
    assert_eq!(unwrap_token_value(it.next()), 32.into());
    expect_lexer_end(&mut it);

    // Test that 2^64 produces an overflow error but that 2^64-1 correctly parses (even if it might
    // produce an error down the line).
    let mut it = Lexer::new("18446744073709551616");
    assert_eq!(unwrap_error(it.next()), PreprocessorError::IntegerOverflow);
    let mut it = Lexer::new("18446744073709551615");
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Integer(Integer {
            value: 18446744073709551615,
            signed: true,
            width: 32
        })
    );
    expect_lexer_end(&mut it);

    // Check that the 16bit or 64bit suffixes produce errors (for now).
    let mut it = Lexer::new("13s");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::NotSupported16BitLiteral
    );
    let mut it = Lexer::new("13S");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::NotSupported16BitLiteral
    );
    let mut it = Lexer::new("13l");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::NotSupported64BitLiteral
    );
    let mut it = Lexer::new("13L");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::NotSupported64BitLiteral
    );

    // Check that they produce unsupported errors even if they happen with a unsigned suffix too.
    let mut it = Lexer::new("13uS");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::NotSupported16BitLiteral
    );
    let mut it = Lexer::new("13Ul");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::NotSupported64BitLiteral
    );
}

#[test]
fn lex_hexadecimal() {
    // Test some basic cases
    let mut it = Lexer::new("0x1 0X0u 0xBaFfe 0XcaFeU");
    assert_eq!(unwrap_token_value(it.next()), 1.into());
    assert_eq!(unwrap_token_value(it.next()), 0u32.into());
    let token = unwrap_token(it.next());
    assert_eq!(token.value, 0xBAFFE.into());
    assert_eq!(token.location, l(1, 9..16),);
    let token = unwrap_token(it.next());
    assert_eq!(token.value, 0xCAFEu32.into());
    assert_eq!(token.location, l(1, 17..24),);
    expect_lexer_end(&mut it);

    // Test with redundant zeroes
    let mut it = Lexer::new("0x000 0x000000000000001");
    assert_eq!(unwrap_token_value(it.next()), 0.into());
    assert_eq!(unwrap_token_value(it.next()), 1.into());
    expect_lexer_end(&mut it);

    // Test splitting with identifiers
    let mut it = Lexer::new("0x31zb");
    assert_eq!(unwrap_token_value(it.next()), 0x31.into());
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("zb".to_string())
    );
    expect_lexer_end(&mut it);

    // Test splitting with whitespace
    let mut it = Lexer::new("0x31/**/32");
    assert_eq!(unwrap_token_value(it.next()), 0x31.into());
    assert_eq!(unwrap_token_value(it.next()), 32.into());
    expect_lexer_end(&mut it);

    // Test splitting with punctuation
    let mut it = Lexer::new("0x31+32");
    assert_eq!(unwrap_token_value(it.next()), 0x31.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::Plus.into());
    assert_eq!(unwrap_token_value(it.next()), 32.into());
    expect_lexer_end(&mut it);

    // Test that 2^64 produces an overflow error but that 2^64-1 correctly parses (even if it might
    // produce an error down the line).
    let mut it = Lexer::new("0x10000000000000000");
    assert_eq!(unwrap_error(it.next()), PreprocessorError::IntegerOverflow);
    let mut it = Lexer::new("0xFFFFFFFFFFFFFFFF");
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Integer(Integer {
            value: 18446744073709551615,
            signed: true,
            width: 32
        })
    );
    expect_lexer_end(&mut it);
}

#[test]
fn lex_octal() {
    // Test some basic cases
    let mut it = Lexer::new("01 00u 07654 01234u");
    assert_eq!(unwrap_token_value(it.next()), 1.into());
    assert_eq!(unwrap_token_value(it.next()), 0u32.into());
    assert_eq!(unwrap_token_value(it.next()), 4012.into());
    assert_eq!(unwrap_token_value(it.next()), 668u32.into());
    expect_lexer_end(&mut it);

    // Test with redundant zeroes
    let mut it = Lexer::new("0000 0000000000000001");
    assert_eq!(unwrap_token_value(it.next()), 0.into());
    assert_eq!(unwrap_token_value(it.next()), 1.into());
    expect_lexer_end(&mut it);

    // Test splitting with identifiers
    let mut it = Lexer::new("031zb");
    assert_eq!(unwrap_token_value(it.next()), 25.into());
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("zb".to_string())
    );
    expect_lexer_end(&mut it);

    // Test splitting with whitespace
    let mut it = Lexer::new("031/**/32");
    assert_eq!(unwrap_token_value(it.next()), 25.into());
    assert_eq!(unwrap_token_value(it.next()), 32.into());
    expect_lexer_end(&mut it);

    // TODO(kangz): Fix octal numbers consuming 8 and 9s as well. This can be done with extra logic
    // already but is not worth the complexity.

    // Test splitting with 8 and 9
    // let mut it = Lexer::new("039 038");
    // assert_eq!(unwrap_token_value(it.next()), 3.into());
    // assert_eq!(unwrap_token_value(it.next()), 9.into());
    // assert_eq!(unwrap_token_value(it.next()), 3.into());
    // assert_eq!(unwrap_token_value(it.next()), 8.into());
    // expect_lexer_end(&mut it);

    // Test splitting with punctuation
    let mut it = Lexer::new("031+32");
    assert_eq!(unwrap_token_value(it.next()), 25.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::Plus.into());
    assert_eq!(unwrap_token_value(it.next()), 32.into());
    expect_lexer_end(&mut it);

    // Test that 2^64 produces an overflow error but that 2^64-1 correctly parses (even if it might
    // produce an error down the line).
    let mut it = Lexer::new("02000000000000000000000");
    assert_eq!(unwrap_error(it.next()), PreprocessorError::IntegerOverflow);
    let mut it = Lexer::new("01777777777777777777777");
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Integer(Integer {
            value: 18446744073709551615,
            signed: true,
            width: 32
        })
    );
    expect_lexer_end(&mut it);
}

#[test]
fn lex_float() {
    // Test a couple simple cases.
    let mut it = Lexer::new("1.0 0.0");
    assert_eq!(unwrap_token_value(it.next()), 1.0f32.into());
    assert_eq!(unwrap_token_value(it.next()), 0.0f32.into());
    expect_lexer_end(&mut it);

    // Test parsing with a leading .
    let mut it = Lexer::new(".99 0.01 .00000000");
    assert_eq!(unwrap_token_value(it.next()), 0.99f32.into());
    assert_eq!(unwrap_token_value(it.next()), 0.01f32.into());
    assert_eq!(unwrap_token_value(it.next()), 0.0f32.into());
    expect_lexer_end(&mut it);

    // Test parsing with nothing after the .
    let mut it = Lexer::new("42. 0.");
    assert_eq!(unwrap_token_value(it.next()), 42.0f32.into());
    assert_eq!(unwrap_token_value(it.next()), 0.0f32.into());
    expect_lexer_end(&mut it);

    // Test parsing with the float suffix
    let mut it = Lexer::new("1000.f 1.f .2f");
    assert_eq!(unwrap_token_value(it.next()), 1000.0f32.into());
    assert_eq!(unwrap_token_value(it.next()), 1.0f32.into());
    assert_eq!(unwrap_token_value(it.next()), 0.2f32.into());
    expect_lexer_end(&mut it);

    // Test parsing with exponents
    //  - with / without float suffixes
    //  - at different points in the float parsing.
    let mut it = Lexer::new("3e10 4.1e-10f .01e12F 4.1e+10f");
    assert_eq!(unwrap_token_value(it.next()), 3e10f32.into());
    assert_eq!(unwrap_token_value(it.next()), 4.1e-10f32.into());
    assert_eq!(unwrap_token_value(it.next()), 0.01e12f32.into());
    assert_eq!(unwrap_token_value(it.next()), 4.1e+10f32.into());
    expect_lexer_end(&mut it);

    // Test parsing with exponents
    //  - After values looking like octal integer (works)
    //  - After values looking like hexadecimal integer (doesn't work)
    let mut it = Lexer::new("05e2 0x1e-2");
    assert_eq!(unwrap_token_value(it.next()), 5e2f32.into());

    assert_eq!(unwrap_token_value(it.next()), 0x1Ei32.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::Minus.into());
    assert_eq!(unwrap_token_value(it.next()), 2i32.into());

    // Test parsing with nothing valid after the 'e' (technically it shouldn't
    // be an error, but there's no language where that sequence of token is
    // valid.
    let mut it = Lexer::new("1.0e");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::FloatParsingError
    );

    // Check that 16bit and 64bit suffixes produce errors
    let mut it = Lexer::new("1.0l");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::NotSupported64BitLiteral
    );
    let mut it = Lexer::new("1.0L");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::NotSupported64BitLiteral
    );
    let mut it = Lexer::new("1.0h");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::NotSupported16BitLiteral
    );
    let mut it = Lexer::new("1.0H");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::NotSupported16BitLiteral
    );
}

#[test]
fn lex_punctuation() {
    // Test parsing some of the token (but not all, that'd be too many tests!)
    let mut it = Lexer::new("+ != <<=");
    assert_eq!(unwrap_token_value(it.next()), Punct::Plus.into());
    let token = unwrap_token(it.next());
    assert_eq!(token.value, Punct::NotEqual.into());
    assert_eq!(token.location, l(1, 2..4),);
    let token = unwrap_token(it.next());
    assert_eq!(token.value, Punct::LeftShiftAssign.into());
    assert_eq!(token.location, l(1, 5..8),);
    expect_lexer_end(&mut it);

    // Test parsing a token that's a prefix of another one just before EOF
    let mut it = Lexer::new("<");
    assert_eq!(unwrap_token_value(it.next()), Punct::LeftAngle.into());
    expect_lexer_end(&mut it);

    // Test \\\n doesn't split the token
    let mut it = Lexer::new("=\\\n=");
    assert_eq!(unwrap_token_value(it.next()), Punct::EqualEqual.into());
    expect_lexer_end(&mut it);

    // Test whitespace splits the token
    let mut it = Lexer::new("+/**/=");
    assert_eq!(unwrap_token_value(it.next()), Punct::Plus.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::Equal.into());
    expect_lexer_end(&mut it);

    // Test a number stops processing the token
    let mut it = Lexer::new("!1");
    assert_eq!(unwrap_token_value(it.next()), Punct::Bang.into());
    assert_eq!(unwrap_token_value(it.next()), 1.into());
    expect_lexer_end(&mut it);

    // Test an identifier stops processing the token
    let mut it = Lexer::new("&a");
    assert_eq!(unwrap_token_value(it.next()), Punct::Ampersand.into());
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("a".to_string())
    );
    expect_lexer_end(&mut it);

    // Test whitespace splits the token
    let mut it = Lexer::new(">/**/>");
    assert_eq!(unwrap_token_value(it.next()), Punct::RightAngle.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::RightAngle.into());
    expect_lexer_end(&mut it);

    // Test that tokens are parsed greedily: `a+++++b` is `a ++ ++ + b` (invalid GLSL) and not
    // `(a ++) + (++ b)` (valid GLSL)
    let mut it = Lexer::new("+++++");
    assert_eq!(unwrap_token_value(it.next()), Punct::Increment.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::Increment.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::Plus.into());
    expect_lexer_end(&mut it);

    // Test that an invalid char produces and error
    let mut it = Lexer::new("@");
    assert_eq!(
        unwrap_error(it.next()),
        PreprocessorError::UnexpectedCharacter
    );

    // Extra punctuation tests for code coverage.
    let mut it = Lexer::new("<= >= += -= &= || |= | ^= { } ] ? .");
    assert_eq!(unwrap_token_value(it.next()), Punct::LessEqual.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::GreaterEqual.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::AddAssign.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::SubAssign.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::AndAssign.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::LogicalOr.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::OrAssign.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::Pipe.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::XorAssign.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::LeftBrace.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::RightBrace.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::RightBracket.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::Question.into());
    assert_eq!(unwrap_token_value(it.next()), Punct::Dot.into());
    expect_lexer_end(&mut it);
}

#[test]
fn lex_had_comments() {
    // Test that had_comments doesn't get set to true if there is no comments.
    let mut it = Lexer::new("#version");
    assert!(!it.had_comments());
    assert_eq!(unwrap_token_value(it.next()), TokenValue::Hash);
    assert!(!it.had_comments());
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("version".to_string())
    );
    assert!(!it.had_comments());
    expect_lexer_end(&mut it);

    // Test that had_comments doesn't get triggered by its sentinel value of '\r'
    let mut it = Lexer::new("\r!");
    assert!(!it.had_comments());
    assert_eq!(unwrap_token_value(it.next()), TokenValue::NewLine);
    assert_eq!(unwrap_token_value(it.next()), Punct::Bang.into());
    assert!(!it.had_comments());
    expect_lexer_end(&mut it);

    // Test that had_comments gets triggered by // comments
    let mut it = Lexer::new("//\n!");
    assert!(!it.had_comments());
    assert_eq!(unwrap_token_value(it.next()), TokenValue::NewLine);
    assert!(it.had_comments());
    assert_eq!(unwrap_token_value(it.next()), Punct::Bang.into());
    assert!(it.had_comments());
    expect_lexer_end(&mut it);

    // Test that had_comments doesn't gets triggered by /**/ comments
    let mut it = Lexer::new("/**/#version");
    assert!(!it.had_comments());
    assert_eq!(unwrap_token_value(it.next()), TokenValue::Hash);
    assert!(it.had_comments());
    assert_eq!(
        unwrap_token_value(it.next()),
        TokenValue::Ident("version".to_string())
    );
    assert!(it.had_comments());
    expect_lexer_end(&mut it);
}

// TODO test has_whitespace