pub mod preprocessor;
use crate::{GlslVersion, Span, SpanEncoding, Spanned};
pub type TokenStream = Vec<Spanned<Token>>;
pub fn parse(source: &str) -> Result<(TokenStream, Metadata), ParseErr> {
let mut lexer: Lexer<Utf32> = Lexer::new(source, SpanEncoding::Utf32);
let tokens = parse_tokens(&mut lexer, false, false);
match lexer.metadata.version {
GlslVersion::Unsupported => {
Err(ParseErr::UnsupportedVersion(lexer.metadata.version))
}
_ => Ok((tokens, lexer.metadata)),
}
}
pub fn parse_with_version(
source: &str,
version: GlslVersion,
) -> (TokenStream, Metadata) {
let mut lexer: Lexer<Utf32> = Lexer::new(source, SpanEncoding::Utf32);
lexer.metadata.version = version;
let tokens = parse_tokens(&mut lexer, false, true);
(tokens, lexer.metadata)
}
pub fn parse_with_utf_16_offsets(
source: &str,
) -> Result<(TokenStream, Metadata), ParseErr> {
let mut lexer: Lexer<Utf16> = Lexer::new(source, SpanEncoding::Utf16);
let tokens = parse_tokens(&mut lexer, false, false);
match lexer.metadata.version {
GlslVersion::Unsupported => {
Err(ParseErr::UnsupportedVersion(lexer.metadata.version))
}
_ => Ok((tokens, lexer.metadata)),
}
}
pub fn parse_with_utf_16_offsets_and_version(
source: &str,
version: GlslVersion,
) -> (TokenStream, Metadata) {
let mut lexer: Lexer<Utf16> = Lexer::new(source, SpanEncoding::Utf16);
lexer.metadata.version = version;
let tokens = parse_tokens(&mut lexer, false, true);
(tokens, lexer.metadata)
}
pub fn parse_with_utf_8_offsets(
source: &str,
) -> Result<(TokenStream, Metadata), ParseErr> {
let mut lexer: Lexer<Utf8> = Lexer::new(source, SpanEncoding::Utf8);
let tokens = parse_tokens(&mut lexer, false, false);
match lexer.metadata.version {
GlslVersion::Unsupported => {
Err(ParseErr::UnsupportedVersion(lexer.metadata.version))
}
_ => Ok((tokens, lexer.metadata)),
}
}
pub fn parse_with_utf_8_offsets_and_version(
source: &str,
version: GlslVersion,
) -> (TokenStream, Metadata) {
let mut lexer: Lexer<Utf8> = Lexer::new(source, SpanEncoding::Utf8);
lexer.metadata.version = version;
let tokens = parse_tokens(&mut lexer, false, true);
(tokens, lexer.metadata)
}
#[derive(Debug)]
pub enum ParseErr {
UnsupportedVersion(GlslVersion),
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Metadata {
pub span_encoding: SpanEncoding,
pub version: GlslVersion,
pub contains_conditional_directives: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Num {
type_: NumType,
num: String,
suffix: Option<String>,
},
Bool(bool),
Ident(String),
Directive(preprocessor::TokenStream),
MacroConcat,
LineComment(String),
BlockComment {
str: String,
contains_eof: bool,
},
Invalid(char),
If,
Else,
For,
Do,
While,
Continue,
Switch,
Case,
Default,
Break,
Return,
Discard,
Struct,
Subroutine,
Reserved(String),
Const,
In,
Out,
InOut,
Attribute,
Uniform,
Varying,
Buffer,
Shared,
Centroid,
Sample,
Patch,
Layout,
Flat,
Smooth,
NoPerspective,
HighP,
MediumP,
LowP,
Invariant,
Precise,
Coherent,
Volatile,
Restrict,
Readonly,
Writeonly,
Op(OpTy),
Comma,
Dot,
Semi,
Colon,
Question,
LParen,
RParen,
LBracket,
RBracket,
LBrace,
RBrace,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumType {
Dec,
Oct,
Hex,
Float,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpTy {
Add,
Sub,
Mul,
Div,
Rem,
And,
Or,
Xor,
LShift,
RShift,
Flip,
Eq,
AddAdd,
SubSub,
AddEq,
SubEq,
MulEq,
DivEq,
RemEq,
AndEq,
OrEq,
XorEq,
LShiftEq,
RShiftEq,
EqEq,
NotEq,
Not,
Gt,
Lt,
Ge,
Le,
AndAnd,
OrOr,
XorXor,
}
impl Token {
pub fn non_semantic_colour(&self) -> crate::syntax::SyntaxType {
use crate::syntax::SyntaxType;
match self {
Token::Num { .. } => SyntaxType::Number,
Token::Bool(_) => SyntaxType::Boolean,
Token::Ident(_) => SyntaxType::Ident,
Token::Directive(_) => SyntaxType::Directive,
Token::MacroConcat => SyntaxType::DirectiveConcat,
Token::LineComment(_) | Token::BlockComment { .. } => {
SyntaxType::Comment
}
Token::Invalid(_) => SyntaxType::Invalid,
Token::If
| Token::Else
| Token::For
| Token::Do
| Token::While
| Token::Continue
| Token::Switch
| Token::Case
| Token::Default
| Token::Break
| Token::Return
| Token::Discard
| Token::Struct
| Token::Subroutine
| Token::Reserved(_)
| Token::Const
| Token::In
| Token::Out
| Token::InOut
| Token::Attribute
| Token::Uniform
| Token::Varying
| Token::Buffer
| Token::Shared
| Token::Centroid
| Token::Sample
| Token::Patch
| Token::Layout
| Token::Flat
| Token::Smooth
| Token::NoPerspective
| Token::HighP
| Token::MediumP
| Token::LowP
| Token::Invariant
| Token::Precise
| Token::Coherent
| Token::Volatile
| Token::Restrict
| Token::Readonly
| Token::Writeonly => SyntaxType::Keyword,
Token::Op(_) => SyntaxType::Operator,
Token::Comma
| Token::Dot
| Token::Semi
| Token::Colon
| Token::Question
| Token::LParen
| Token::RParen
| Token::LBracket
| Token::RBracket
| Token::LBrace
| Token::RBrace => SyntaxType::Punctuation,
}
}
pub fn can_start_statement(&self) -> bool {
match self {
Self::If
| Self::For
| Self::Do
| Self::While
| Self::Continue
| Self::Switch
| Self::Break
| Self::Return
| Self::Discard
| Self::Struct
| Self::Subroutine
| Self::Const
| Self::In
| Self::Out
| Self::InOut
| Self::Attribute
| Self::Uniform
| Self::Varying
| Self::Buffer
| Self::Shared
| Self::Centroid
| Self::Sample
| Self::Patch
| Self::Layout
| Self::Flat
| Self::Smooth
| Self::NoPerspective
| Self::HighP
| Self::MediumP
| Self::LowP
| Self::Invariant
| Self::Precise
| Self::Coherent
| Self::Volatile
| Self::Restrict
| Self::Readonly
| Self::Writeonly => true,
_ => false,
}
}
}
impl std::fmt::Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Token::Num { type_, num, suffix } => {
match type_ {
NumType::Dec => {}
NumType::Oct => write!(f, "0")?,
NumType::Hex => write!(f, "0x")?,
NumType::Float => {}
}
write!(f, "{num}")?;
if let Some(suffix) = suffix {
write!(f, "{suffix}")
} else {
Ok(())
}
}
Token::Bool(b) => write!(f, "{b}"),
Token::Ident(s) => write!(f, "{s}"),
Token::Directive(_) => write!(f, "DIRECTIVE"),
Token::MacroConcat => write!(f, "##"),
Token::LineComment(s) => write!(f, "//{s}"),
Token::BlockComment { str, contains_eof } => {
write!(f, "/*{str}")?;
if *contains_eof {
write!(f, "*/")
} else {
Ok(())
}
}
Token::Invalid(c) => write!(f, "{c}"),
Token::If => write!(f, "if"),
Token::Else => write!(f, "else"),
Token::For => write!(f, "for"),
Token::Do => write!(f, "do"),
Token::While => write!(f, "while"),
Token::Continue => write!(f, "continue"),
Token::Switch => write!(f, "switch"),
Token::Case => write!(f, "case"),
Token::Default => write!(f, "default"),
Token::Break => write!(f, "break"),
Token::Return => write!(f, "return"),
Token::Discard => write!(f, "discard"),
Token::Struct => write!(f, "struct"),
Token::Subroutine => write!(f, "subroutine"),
Token::Reserved(s) => write!(f, "{s}"),
Token::Const => write!(f, "const"),
Token::In => write!(f, "in"),
Token::Out => write!(f, "out"),
Token::InOut => write!(f, "inout"),
Token::Attribute => write!(f, "attribute"),
Token::Uniform => write!(f, "uniform"),
Token::Varying => write!(f, "varying"),
Token::Buffer => write!(f, "buffer"),
Token::Shared => write!(f, "shared"),
Token::Centroid => write!(f, "centroid"),
Token::Sample => write!(f, "sample"),
Token::Patch => write!(f, "patch"),
Token::Layout => write!(f, "layout"),
Token::Flat => write!(f, "flat"),
Token::Smooth => write!(f, "smooth"),
Token::NoPerspective => write!(f, "noperspective"),
Token::HighP => write!(f, "highp"),
Token::MediumP => write!(f, "mediump"),
Token::LowP => write!(f, "lowp"),
Token::Invariant => write!(f, "invariant"),
Token::Precise => write!(f, "precise"),
Token::Coherent => write!(f, "coherent"),
Token::Volatile => write!(f, "volatile"),
Token::Restrict => write!(f, "restrict"),
Token::Readonly => write!(f, "readonly"),
Token::Writeonly => write!(f, "writeonly"),
Token::Op(op) => match op {
OpTy::Add => write!(f, "+"),
OpTy::Sub => write!(f, "-"),
OpTy::Mul => write!(f, "*"),
OpTy::Div => write!(f, "/"),
OpTy::Rem => write!(f, "%"),
OpTy::And => write!(f, "&"),
OpTy::Or => write!(f, "|"),
OpTy::Xor => write!(f, "^"),
OpTy::LShift => write!(f, "<<"),
OpTy::RShift => write!(f, ">>"),
OpTy::Flip => write!(f, "~"),
OpTy::Eq => write!(f, "="),
OpTy::AddAdd => write!(f, "++"),
OpTy::SubSub => write!(f, "--"),
OpTy::AddEq => write!(f, "+="),
OpTy::SubEq => write!(f, "-="),
OpTy::MulEq => write!(f, "*="),
OpTy::DivEq => write!(f, "/="),
OpTy::RemEq => write!(f, "%="),
OpTy::AndEq => write!(f, "&="),
OpTy::OrEq => write!(f, "|="),
OpTy::XorEq => write!(f, "^="),
OpTy::LShiftEq => write!(f, "<<="),
OpTy::RShiftEq => write!(f, ">>="),
OpTy::EqEq => write!(f, "=="),
OpTy::NotEq => write!(f, "!="),
OpTy::Not => write!(f, "!"),
OpTy::Gt => write!(f, ">"),
OpTy::Lt => write!(f, "<"),
OpTy::Ge => write!(f, ">="),
OpTy::Le => write!(f, "<="),
OpTy::AndAnd => write!(f, "&&"),
OpTy::OrOr => write!(f, "||"),
OpTy::XorXor => write!(f, "^^"),
},
Token::Comma => write!(f, ","),
Token::Dot => write!(f, "."),
Token::Semi => write!(f, ";"),
Token::Colon => write!(f, ":"),
Token::Question => write!(f, "?"),
Token::LParen => write!(f, "("),
Token::RParen => write!(f, ")"),
Token::LBracket => write!(f, "["),
Token::RBracket => write!(f, "]"),
Token::LBrace => write!(f, "{{"),
Token::RBrace => write!(f, "}}"),
}
}
}
fn parse_tokens<C: Char>(
lexer: &mut Lexer<C>,
parsing_define_body: bool,
hardcoded_version: bool,
) -> TokenStream {
let mut tokens = Vec::new();
let mut can_start_directive = true;
let mut buffer = String::new();
let mut parsed_directive_yet = false;
'outer: while !lexer.is_done() {
let buffer_start = lexer.position();
let mut current = match lexer.peek() {
Some(c) => c,
None => {
break;
}
};
if parsing_define_body && (current == '\r' || current == '\n') {
return tokens;
}
if is_word_start(¤t) {
can_start_directive = false;
buffer.push(current);
lexer.advance();
'word: loop {
current = match lexer.peek() {
Some(c) => c,
None => {
tokens.push((
match_word(std::mem::take(&mut buffer)),
Span {
start: buffer_start,
end: lexer.position(),
},
));
break 'word;
}
};
if is_word(¤t) {
buffer.push(current);
lexer.advance();
} else {
tokens.push((
match_word(std::mem::take(&mut buffer)),
Span {
start: buffer_start,
end: lexer.position(),
},
));
break 'word;
}
}
} else if is_number_start(¤t) {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NumState {
Zero,
Hex,
Dec,
Float,
}
can_start_directive = false;
let mut num_buffer = String::new();
let mut suffix_buffer = None;
let mut state = if lexer.take_pat("0x") {
NumState::Hex
} else if lexer.take_pat("0X") {
NumState::Hex
} else if current == '0' {
num_buffer.push(current);
lexer.advance();
NumState::Zero
} else if current == '.' {
if let Some(lookahead) = lexer.lookahead_1() {
if lookahead.is_ascii_digit() {
num_buffer.push(current);
lexer.advance();
NumState::Float
} else {
lexer.advance();
tokens.push((
Token::Dot,
Span {
start: buffer_start,
end: lexer.position(),
},
));
continue;
}
} else {
lexer.advance();
tokens.push((
Token::Dot,
Span {
start: buffer_start,
end: lexer.position(),
},
));
continue;
}
} else {
num_buffer.push(current);
lexer.advance();
NumState::Dec
};
'number: loop {
current = match lexer.peek() {
Some(c) => c,
None => {
let type_ = match state {
NumState::Hex => NumType::Hex,
NumState::Zero => {
if num_buffer.as_str() == "0" {
NumType::Dec
} else {
num_buffer.remove(0);
NumType::Oct
}
}
NumState::Dec => NumType::Dec,
NumState::Float => NumType::Float,
};
tokens.push((
Token::Num {
num: num_buffer,
suffix: suffix_buffer,
type_,
},
Span {
start: buffer_start,
end: lexer.position(),
},
));
break 'number;
}
};
if current == '.' && state == NumState::Hex {
tokens.push((
Token::Num {
num: num_buffer,
suffix: suffix_buffer,
type_: NumType::Hex,
},
Span {
start: buffer_start,
end: lexer.position(),
},
));
tokens.push((
Token::Dot,
Span {
start: lexer.position(),
end: lexer.position() + 1,
},
));
lexer.advance();
break 'number;
}
if current == '.' && suffix_buffer.is_some() {
let type_ = match state {
NumState::Hex => NumType::Hex,
NumState::Zero => {
if num_buffer.as_str() == "0" {
NumType::Dec
} else {
num_buffer.remove(0);
NumType::Oct
}
}
NumState::Dec => NumType::Dec,
NumState::Float => NumType::Float,
};
tokens.push((
Token::Num {
num: num_buffer,
suffix: suffix_buffer,
type_,
},
Span {
start: buffer_start,
end: lexer.position(),
},
));
tokens.push((
Token::Dot,
Span {
start: lexer.position(),
end: lexer.position() + 1,
},
));
lexer.advance();
break 'number;
}
if current == '.'
&& (state == NumState::Dec || state == NumState::Zero)
{
state = NumState::Float;
num_buffer.push(current);
lexer.advance();
continue 'number;
}
if current == '.' && state == NumState::Float {
let type_ = match state {
NumState::Hex => NumType::Hex,
NumState::Zero => {
if num_buffer.as_str() == "0" {
NumType::Dec
} else {
num_buffer.remove(0);
NumType::Oct
}
}
NumState::Dec => NumType::Dec,
NumState::Float => NumType::Float,
};
tokens.push((
Token::Num {
num: num_buffer,
suffix: suffix_buffer,
type_,
},
Span {
start: buffer_start,
end: lexer.position(),
},
));
tokens.push((
Token::Dot,
Span {
start: lexer.position(),
end: lexer.position() + 1,
},
));
lexer.advance();
break 'number;
}
if current == 'e' {
if let Some(lookahead) = lexer.lookahead_1() {
if lookahead.is_ascii_digit() {
num_buffer.push(current);
lexer.advance();
state = NumState::Float;
continue 'number;
} else if lookahead == '+' || lookahead == '-' {
if let Some(lookahead_2) = lexer.lookahead_2() {
if lookahead_2.is_ascii_digit() {
num_buffer.push(current);
num_buffer.push(lookahead);
lexer.advance();
lexer.advance();
state = NumState::Float;
continue 'number;
} else {
lexer.advance();
suffix_buffer = Some(String::from(current));
let type_ = match state {
NumState::Hex => NumType::Hex,
NumState::Zero => {
if num_buffer.as_str() == "0" {
NumType::Dec
} else {
num_buffer.remove(0);
NumType::Oct
}
}
NumState::Dec => NumType::Dec,
NumState::Float => NumType::Float,
};
tokens.push((
Token::Num {
num: num_buffer,
suffix: suffix_buffer,
type_,
},
Span {
start: buffer_start,
end: lexer.position(),
},
));
break 'number;
}
} else {
suffix_buffer = Some(String::from(current));
lexer.advance();
let type_ = match state {
NumState::Hex => NumType::Hex,
NumState::Zero => {
if num_buffer.as_str() == "0" {
NumType::Dec
} else {
num_buffer.remove(0);
NumType::Oct
}
}
NumState::Dec => NumType::Dec,
NumState::Float => NumType::Float,
};
tokens.push((
Token::Num {
num: num_buffer,
suffix: suffix_buffer,
type_,
},
Span {
start: buffer_start,
end: lexer.position(),
},
));
break 'number;
}
}
}
}
if current.is_ascii_hexdigit() || is_word(¤t) {
match state {
NumState::Zero | NumState::Dec | NumState::Float => {
if !current.is_ascii_digit()
&& suffix_buffer.is_none()
{
suffix_buffer = Some(String::new());
}
}
NumState::Hex => {
if !current.is_ascii_hexdigit()
&& suffix_buffer.is_none()
{
suffix_buffer = Some(String::new());
}
}
}
if let Some(suffix) = &mut suffix_buffer {
suffix.push(current);
} else {
num_buffer.push(current);
}
lexer.advance();
} else {
let type_ = match state {
NumState::Hex => NumType::Hex,
NumState::Zero => {
if num_buffer.as_str() == "0" {
NumType::Dec
} else {
num_buffer.remove(0);
NumType::Oct
}
}
NumState::Dec => NumType::Dec,
NumState::Float => NumType::Float,
};
tokens.push((
Token::Num {
num: num_buffer,
suffix: suffix_buffer,
type_,
},
Span {
start: buffer_start,
end: lexer.position(),
},
));
break 'number;
}
}
} else if is_punctuation_start(¤t) {
can_start_directive = false;
if lexer.take_pat("//") {
'line_comment: loop {
current = match lexer.peek() {
Some(c) => c,
None => {
tokens.push((
Token::LineComment(std::mem::take(&mut buffer)),
Span {
start: buffer_start,
end: lexer.position(),
},
));
break 'line_comment;
}
};
if current == '\r' || current == '\n' {
tokens.push((
Token::LineComment(std::mem::take(&mut buffer)),
Span {
start: buffer_start,
end: lexer.position(),
},
));
break 'line_comment;
} else {
buffer.push(current);
lexer.advance();
}
}
} else if lexer.take_pat("/*") {
'comment: loop {
if lexer.take_pat("*/") {
tokens.push((
Token::BlockComment {
str: std::mem::take(&mut buffer),
contains_eof: false,
},
Span {
start: buffer_start,
end: lexer.position(),
},
));
can_start_directive = true;
break 'comment;
}
if let Some(char) = lexer.next() {
buffer.push(char);
} else {
tokens.push((
Token::BlockComment {
str: std::mem::take(&mut buffer),
contains_eof: true,
},
Span {
start: buffer_start,
end: lexer.position(),
},
));
break 'comment;
}
}
} else {
tokens.push((
match_punctuation(lexer),
Span {
start: buffer_start,
end: lexer.position(),
},
));
}
} else if current.is_whitespace() {
if current == '\r' || current == '\n' {
can_start_directive = true;
}
lexer.advance();
} else if can_start_directive && current == '#' && !parsing_define_body
{
let mut first_directive = false;
if !parsed_directive_yet {
first_directive = true;
for (token, _) in &tokens {
match token {
Token::LineComment(_) | Token::BlockComment { .. } => {}
_ => {
first_directive = false;
break;
}
}
}
parsed_directive_yet = true;
};
let directive_start = lexer.position();
lexer.advance();
loop {
current = match lexer.peek() {
Some(c) => c,
None => {
tokens.push((
Token::Directive(preprocessor::TokenStream::Empty),
Span {
start: directive_start,
end: lexer.position(),
},
));
break 'outer;
}
};
if current == '\r' || current == '\n' {
tokens.push((
Token::Directive(preprocessor::TokenStream::Empty),
Span {
start: directive_start,
end: lexer.position(),
},
));
continue 'outer;
}
if current.is_ascii_whitespace() {
lexer.advance();
continue;
} else {
break;
}
}
if !is_word_start(¤t) {
let content_start = lexer.position();
'content: loop {
current = match lexer.peek() {
Some(c) => c,
None => {
tokens.push((
Token::Directive(
preprocessor::TokenStream::Invalid {
content: (
std::mem::take(&mut buffer),
Span {
start: content_start,
end: lexer.position(),
},
),
},
),
Span {
start: directive_start,
end: lexer.position(),
},
));
break 'outer;
}
};
if current == '\r' || current == '\n' {
break 'content;
} else {
buffer.push(current);
lexer.advance();
}
}
tokens.push((
Token::Directive(preprocessor::TokenStream::Invalid {
content: (
std::mem::take(&mut buffer),
Span {
start: content_start,
end: lexer.position(),
},
),
}),
Span {
start: directive_start,
end: lexer.position(),
},
));
continue 'outer;
}
let directive_kw_start = lexer.position();
buffer.push(current);
lexer.advance();
'directive_name: loop {
current = match lexer.peek() {
Some(c) => c,
None => {
tokens.push((
Token::Directive(preprocessor::construct_empty(
lexer,
buffer,
Span {
start: directive_kw_start,
end: lexer.position(),
},
)),
Span {
start: directive_start,
end: lexer.position(),
},
));
break 'outer;
}
};
if is_word(¤t) {
buffer.push(current);
lexer.advance();
} else {
break 'directive_name;
}
}
let directive_kw_span = Span {
start: directive_kw_start,
end: lexer.position(),
};
match buffer.as_ref() {
"version" => {
let (stream, version) = preprocessor::parse_version(
lexer,
directive_kw_span,
first_directive,
);
tokens.push((
Token::Directive(stream),
Span {
start: directive_start,
end: lexer.position(),
},
));
if first_directive && !hardcoded_version {
if let Some(version) = version {
if version == GlslVersion::Unsupported {
break 'outer;
} else {
lexer.metadata.version = version;
}
}
}
}
"extension" => tokens.push((
Token::Directive(preprocessor::parse_extension(
lexer,
directive_kw_span,
)),
Span {
start: directive_start,
end: lexer.position(),
},
)),
"line" => tokens.push((
Token::Directive(preprocessor::parse_line(
lexer,
directive_kw_span,
)),
Span {
start: directive_start,
end: lexer.position(),
},
)),
"define" => {
tokens.push((
Token::Directive(preprocessor::TokenStream::Define {
kw: directive_kw_span,
ident_tokens: preprocessor::parse_define(lexer),
body_tokens: parse_tokens(
lexer,
true,
hardcoded_version,
),
}),
Span {
start: directive_start,
end: lexer.position(),
},
));
}
"undef" => tokens.push((
Token::Directive(preprocessor::parse_undef(
lexer,
directive_kw_span,
)),
Span {
start: directive_start,
end: lexer.position(),
},
)),
"ifdef" | "ifndef" | "if" | "elif" | "else" | "endif" => {
lexer.metadata.contains_conditional_directives = true;
tokens.push((
Token::Directive(preprocessor::parse_condition(
lexer,
&buffer,
directive_kw_span,
)),
Span {
start: directive_start,
end: lexer.position(),
},
));
}
"error" => {
buffer.clear();
let content_start = lexer.position();
'content: loop {
current = match lexer.peek() {
Some(c) => c,
None => {
break 'content;
}
};
if current == '\r' || current == '\n' {
break 'content;
} else {
buffer.push(current);
lexer.advance();
}
}
tokens.push((
Token::Directive(preprocessor::TokenStream::Error {
kw: directive_kw_span,
message: Some((
std::mem::take(&mut buffer),
Span {
start: content_start,
end: lexer.position(),
},
)),
}),
Span {
start: directive_start,
end: lexer.position(),
},
));
}
"pragma" => {
buffer.clear();
let content_start = lexer.position();
'content: loop {
current = match lexer.peek() {
Some(c) => c,
None => {
break 'content;
}
};
if current == '\r' || current == '\n' {
break 'content;
} else {
buffer.push(current);
lexer.advance();
}
}
tokens.push((
Token::Directive(preprocessor::TokenStream::Pragma {
kw: directive_kw_span,
options: Some((
std::mem::take(&mut buffer),
Span {
start: content_start,
end: lexer.position(),
},
)),
}),
Span {
start: directive_start,
end: lexer.position(),
},
));
}
_ => {
let kw = (std::mem::take(&mut buffer), directive_kw_span);
let content_start = lexer.position();
'content: loop {
current = match lexer.peek() {
Some(c) => c,
None => {
break 'content;
}
};
if current == '\r' || current == '\n' {
break 'content;
} else {
buffer.push(current);
lexer.advance();
}
}
tokens.push((
Token::Directive(preprocessor::TokenStream::Custom {
kw,
content: Some((
std::mem::take(&mut buffer),
Span {
start: content_start,
end: lexer.position(),
},
)),
}),
Span {
start: directive_start,
end: lexer.position(),
},
));
}
}
buffer.clear();
} else if current == '#' && parsing_define_body {
if lexer.take_pat("##") {
tokens.push((
Token::MacroConcat,
Span {
start: buffer_start,
end: lexer.position(),
},
));
} else {
lexer.advance();
tokens.push((
Token::Invalid(current),
Span {
start: buffer_start,
end: lexer.position(),
},
));
}
} else {
lexer.advance();
tokens.push((
Token::Invalid(current),
Span {
start: buffer_start,
end: lexer.position(),
},
));
}
}
tokens
}
pub(crate) trait Char {
fn offset(c: char) -> usize;
}
pub(crate) struct Utf8;
impl Char for Utf8 {
#[inline]
fn offset(c: char) -> usize {
c.len_utf8()
}
}
pub(crate) struct Utf16;
impl Char for Utf16 {
#[inline]
fn offset(c: char) -> usize {
c.len_utf16()
}
}
pub(crate) struct Utf32;
impl Char for Utf32 {
#[inline]
fn offset(_: char) -> usize {
1
}
}
pub(crate) struct Lexer<C> {
chars: Vec<char>,
cursor: usize,
offset: usize,
metadata: Metadata,
_marker: std::marker::PhantomData<C>,
}
impl<C: Char> Lexer<C> {
pub(crate) fn new(source: &str, span_encoding: SpanEncoding) -> Self {
let mut lexer = Lexer {
chars: source.chars().collect(),
cursor: 0,
offset: 0,
metadata: Metadata {
span_encoding,
version: Default::default(),
contains_conditional_directives: false,
},
_marker: Default::default(),
};
let i = lexer.take_line_continuator(0);
lexer.cursor = i;
lexer.offset = i;
lexer
}
fn peek(&self) -> Option<char> {
self.chars.get(self.cursor).map(|c| *c)
}
fn lookahead_1(&self) -> Option<char> {
let pos = self.cursor + 1 + self.take_line_continuator(self.cursor + 1);
self.chars.get(pos).map(|c| *c)
}
fn lookahead_2(&self) -> Option<char> {
let pos = self.cursor + 1 + self.take_line_continuator(self.cursor + 1);
let pos = pos + 1 + self.take_line_continuator(pos + 1);
self.chars.get(pos).map(|c| *c)
}
fn advance(&mut self) {
match self.peek() {
Some(c) => {
self.offset += C::offset(c);
}
None => {}
}
self.cursor += 1;
let i = self.take_line_continuator(self.cursor);
self.offset += i;
self.cursor += i;
}
fn next(&mut self) -> Option<char> {
let c = self.peek();
if c.is_some() {
self.advance();
}
c
}
fn take_pat(&mut self, pat: &str) -> bool {
let pat = pat.chars().collect::<Vec<_>>();
let pat_len = pat.len();
let mut pat_count = 0;
let starting_position = self.cursor;
let starting_offset = self.offset;
if self.chars.len() >= self.cursor + pat_len {
while self.peek().is_some() {
if pat_count == pat_len {
break;
}
if self.peek().unwrap() != pat[pat_count] {
self.cursor = starting_position;
self.offset = starting_offset;
return false;
}
self.advance();
pat_count += 1;
}
return true;
}
false
}
fn position(&self) -> usize {
self.offset
}
fn is_done(&self) -> bool {
self.cursor == self.chars.len()
}
fn take_line_continuator(&self, idx: usize) -> usize {
let current = match self.chars.get(idx) {
Some(c) => *c,
None => return 0,
};
if current != '\\' {
return 0;
}
if let Some(lookahead) = self.chars.get(idx + 1) {
if *lookahead == '\n' {
2
} else if *lookahead == '\r' {
if let Some(lookahead_2) = self.chars.get(idx + 2) {
if *lookahead_2 == '\n' {
3
} else {
2
}
} else {
2
}
} else if *lookahead == '\\' {
0
} else {
0
}
} else {
1
}
}
}
fn is_word_start(c: &char) -> bool {
c.is_ascii_alphabetic() || *c == '_'
}
fn is_word(c: &char) -> bool {
c.is_ascii_alphanumeric() || *c == '_'
}
fn is_number_start(c: &char) -> bool {
c.is_ascii_digit() || *c == '.'
}
fn is_punctuation_start(c: &char) -> bool {
match c {
'=' | ',' | '.' | ';' | '(' | ')' | '[' | ']' | '{' | '}' | ':'
| '+' | '-' | '*' | '/' | '%' | '>' | '<' | '!' | '~' | '?' | '&'
| '|' | '^' => true,
_ => false,
}
}
macro_rules! match_op {
($lexer:ident, $str:expr, $token:expr) => {
if $lexer.take_pat($str) {
return $token;
}
};
}
fn match_punctuation<C: Char>(lexer: &mut Lexer<C>) -> Token {
match_op!(lexer, "<<=", Token::Op(OpTy::LShiftEq));
match_op!(lexer, ">>=", Token::Op(OpTy::RShiftEq));
match_op!(lexer, "==", Token::Op(OpTy::EqEq));
match_op!(lexer, "!=", Token::Op(OpTy::NotEq));
match_op!(lexer, ">=", Token::Op(OpTy::Ge));
match_op!(lexer, "<=", Token::Op(OpTy::Le));
match_op!(lexer, "&&", Token::Op(OpTy::AndAnd));
match_op!(lexer, "||", Token::Op(OpTy::OrOr));
match_op!(lexer, "++", Token::Op(OpTy::AddAdd));
match_op!(lexer, "--", Token::Op(OpTy::SubSub));
match_op!(lexer, "<<", Token::Op(OpTy::LShift));
match_op!(lexer, ">>", Token::Op(OpTy::RShift));
match_op!(lexer, "+=", Token::Op(OpTy::AddEq));
match_op!(lexer, "-=", Token::Op(OpTy::SubEq));
match_op!(lexer, "*=", Token::Op(OpTy::MulEq));
match_op!(lexer, "/=", Token::Op(OpTy::DivEq));
match_op!(lexer, "%=", Token::Op(OpTy::RemEq));
match_op!(lexer, "&=", Token::Op(OpTy::AndEq));
match_op!(lexer, "|=", Token::Op(OpTy::OrEq));
match_op!(lexer, "^^", Token::Op(OpTy::XorXor));
match_op!(lexer, "^=", Token::Op(OpTy::XorEq));
match_op!(lexer, "=", Token::Op(OpTy::Eq));
match_op!(lexer, ";", Token::Semi);
match_op!(lexer, ".", Token::Dot);
match_op!(lexer, ",", Token::Comma);
match_op!(lexer, "(", Token::LParen);
match_op!(lexer, ")", Token::RParen);
match_op!(lexer, "[", Token::LBracket);
match_op!(lexer, "]", Token::RBracket);
match_op!(lexer, "{", Token::LBrace);
match_op!(lexer, "}", Token::RBrace);
match_op!(lexer, "+", Token::Op(OpTy::Add));
match_op!(lexer, "-", Token::Op(OpTy::Sub));
match_op!(lexer, "*", Token::Op(OpTy::Mul));
match_op!(lexer, "/", Token::Op(OpTy::Div));
match_op!(lexer, ">", Token::Op(OpTy::Gt));
match_op!(lexer, "<", Token::Op(OpTy::Lt));
match_op!(lexer, "!", Token::Op(OpTy::Not));
match_op!(lexer, "~", Token::Op(OpTy::Flip));
match_op!(lexer, "?", Token::Question);
match_op!(lexer, ":", Token::Colon);
match_op!(lexer, "%", Token::Op(OpTy::Rem));
match_op!(lexer, "&", Token::Op(OpTy::And));
match_op!(lexer, "|", Token::Op(OpTy::Or));
match_op!(lexer, "^", Token::Op(OpTy::Xor));
unreachable!("[token::match_punctuation] Exhausted all of the patterns without matching anything!");
}
fn match_word(str: String) -> Token {
match str.as_ref() {
"true" => Token::Bool(true),
"false" => Token::Bool(false),
"if" => Token::If,
"else" => Token::Else,
"for" => Token::For,
"do" => Token::Do,
"while" => Token::While,
"continue" => Token::Continue,
"switch" => Token::Switch,
"case" => Token::Case,
"default" => Token::Default,
"break" => Token::Break,
"return" => Token::Return,
"discard" => Token::Discard,
"struct" => Token::Struct,
"subroutine" => Token::Subroutine,
"const" => Token::Const,
"in" => Token::In,
"out" => Token::Out,
"inout" => Token::InOut,
"attribute" => Token::Attribute,
"uniform" => Token::Uniform,
"varying" => Token::Varying,
"buffer" => Token::Buffer,
"shared" => Token::Shared,
"centroid" => Token::Centroid,
"sample" => Token::Sample,
"patch" => Token::Patch,
"layout" => Token::Layout,
"flat" => Token::Flat,
"smooth" => Token::Smooth,
"noperspective" => Token::NoPerspective,
"highp" => Token::HighP,
"mediump" => Token::MediumP,
"lowp" => Token::LowP,
"invariant" => Token::Invariant,
"precise" => Token::Precise,
"coherent" => Token::Coherent,
"volatile" => Token::Volatile,
"restrict" => Token::Restrict,
"readonly" => Token::Readonly,
"writeonly" => Token::Writeonly,
"common" | "partition" | "active" | "asm" | "class" | "union"
| "enum" | "typedef" | "template" | "this" | "resource" | "goto"
| "inline" | "noinline" | "public" | "static" | "extern"
| "external" | "interface" | "long" | "short" | "half" | "fixed"
| "unsigned" | "superp" | "input" | "output" | "hvec2" | "hvec3"
| "hvec4" | "fvec2" | "fvec3" | "fvec4" | "sampler3DRect"
| "filter" | "sizeof" | "cast" | "namespace" | "using" => {
Token::Reserved(str)
}
_ => Token::Ident(str),
}
}
#[cfg(test)]
mod tests {
use super::{NumType, OpTy, Token};
use crate::span;
macro_rules! assert_tokens2 {
($src:expr, $($token:expr),*) => {
let (tokens, _metadata) = crate::lexer::parse_with_version($src, crate::GlslVersion::_450);
assert_eq!(tokens, vec![
$(
$token,
)*
])
};
}
#[test]
fn span_comparisons() {
let src = "a 𐐀 c";
let (tokens, _metadata) =
super::parse_with_version(src, crate::GlslVersion::_450);
assert_eq!(
tokens,
vec![
(Token::Ident("a".into()), span(0, 1)),
(Token::Invalid('𐐀'), span(2, 3)),
(Token::Ident("c".into()), span(4, 5))
]
);
let (tokens, _metadata) = super::parse_with_utf_16_offsets_and_version(
src,
crate::GlslVersion::_450,
);
assert_eq!(
tokens,
vec![
(Token::Ident("a".into()), span(0, 1)),
(Token::Invalid('𐐀'), span(2, 4)),
(Token::Ident("c".into()), span(5, 6))
]
);
let (tokens, _metadata) = super::parse_with_utf_8_offsets_and_version(
src,
crate::GlslVersion::_450,
);
assert_eq!(
tokens,
vec![
(Token::Ident("a".into()), span(0, 1)),
(Token::Invalid('𐐀'), span(2, 6)),
(Token::Ident("c".into()), span(7, 8))
]
);
}
#[test]
fn spans() {
assert_tokens2!("return", (Token::Return, span(0, 6)));
assert_tokens2!("break ", (Token::Break, span(0, 5)));
assert_tokens2!(
"return break",
(Token::Return, span(0, 6)),
(Token::Break, span(7, 12))
);
assert_tokens2!(";", (Token::Semi, span(0, 1)));
assert_tokens2!(": ", (Token::Colon, span(0, 1)));
assert_tokens2!(
"; :",
(Token::Semi, span(0, 1)),
(Token::Colon, span(2, 3))
);
assert_tokens2!(
"// comment",
(Token::LineComment(" comment".into()), span(0, 10))
);
assert_tokens2!(
"/* a */",
(
Token::BlockComment {
str: " a ".into(),
contains_eof: false
},
span(0, 7)
)
);
assert_tokens2!(
"/* a",
(
Token::BlockComment {
str: " a".into(),
contains_eof: true
},
span(0, 4)
)
);
assert_tokens2!("@", (Token::Invalid('@'), span(0, 1)));
assert_tokens2!("¬", (Token::Invalid('¬'), span(0, 1)));
assert_tokens2!(
"@ ¬",
(Token::Invalid('@'), span(0, 1)),
(Token::Invalid('¬'), span(3, 4))
);
assert_tokens2!(".", (Token::Dot, span(0, 1)));
assert_tokens2!(". ", (Token::Dot, span(0, 1)));
assert_tokens2!(
"0xF.",
(
Token::Num {
num: "F".into(),
suffix: None,
type_: NumType::Hex
},
span(0, 3)
),
(Token::Dot, span(3, 4))
);
assert_tokens2!(
"123u.",
(
Token::Num {
num: "123".into(),
suffix: Some("u".into()),
type_: NumType::Dec
},
span(0, 4)
),
(Token::Dot, span(4, 5))
);
assert_tokens2!(
"1.2.",
(
Token::Num {
num: "1.2".into(),
suffix: None,
type_: NumType::Float
},
span(0, 3)
),
(Token::Dot, span(3, 4))
);
assert_tokens2!(
"1e",
(
Token::Num {
num: "1".into(),
suffix: Some("e".into()),
type_: NumType::Dec
},
span(0, 2)
)
);
assert_tokens2!(
"123 ",
(
Token::Num {
num: "123".into(),
suffix: None,
type_: NumType::Dec
},
span(0, 3)
)
);
assert_tokens2!(
"1e+=",
(
Token::Num {
num: "1".into(),
suffix: Some("e".into()),
type_: NumType::Dec
},
span(0, 2)
),
(Token::Op(OpTy::AddEq), span(2, 4))
);
assert_tokens2!(
"1e+",
(
Token::Num {
num: "1".into(),
suffix: Some("e".into()),
type_: NumType::Dec
},
span(0, 2)
),
(Token::Op(OpTy::Add), span(2, 3))
);
}
macro_rules! assert_tokens {
($src:expr, $($token:expr),*) => {
let output = crate::lexer::parse_with_version($src, crate::GlslVersion::_450).0.into_iter().map(|(t, _)| t).collect::<Vec<_>>();
assert_eq!(output, vec![
$(
$token,
)*
])
};
}
#[test]
fn identifiers() {
assert_tokens!("ident", Token::Ident("ident".into()));
assert_tokens!("gl_something", Token::Ident("gl_something".into()));
assert_tokens!("id_145", Token::Ident("id_145".into()));
assert_tokens!("_9ga", Token::Ident("_9ga".into()));
assert_tokens!("my_\\\r\nident", Token::Ident("my_ident".into()));
assert_tokens!("_\\\n9ga", Token::Ident("_9ga".into()));
}
#[test]
fn keywords() {
assert_tokens!("true", Token::Bool(true));
assert_tokens!("false", Token::Bool(false));
assert_tokens!("if", Token::If);
assert_tokens!("else", Token::Else);
assert_tokens!("for", Token::For);
assert_tokens!("do", Token::Do);
assert_tokens!("while", Token::While);
assert_tokens!("continue", Token::Continue);
assert_tokens!("switch", Token::Switch);
assert_tokens!("case", Token::Case);
assert_tokens!("default", Token::Default);
assert_tokens!("break", Token::Break);
assert_tokens!("return", Token::Return);
assert_tokens!("discard", Token::Discard);
assert_tokens!("struct", Token::Struct);
assert_tokens!("subroutine", Token::Subroutine);
assert_tokens!("const", Token::Const);
assert_tokens!("in", Token::In);
assert_tokens!("out", Token::Out);
assert_tokens!("inout", Token::InOut);
assert_tokens!("attribute", Token::Attribute);
assert_tokens!("uniform", Token::Uniform);
assert_tokens!("varying", Token::Varying);
assert_tokens!("buffer", Token::Buffer);
assert_tokens!("shared", Token::Shared);
assert_tokens!("centroid", Token::Centroid);
assert_tokens!("sample", Token::Sample);
assert_tokens!("patch", Token::Patch);
assert_tokens!("layout", Token::Layout);
assert_tokens!("flat", Token::Flat);
assert_tokens!("smooth", Token::Smooth);
assert_tokens!("noperspective", Token::NoPerspective);
assert_tokens!("highp", Token::HighP);
assert_tokens!("mediump", Token::MediumP);
assert_tokens!("lowp", Token::LowP);
assert_tokens!("invariant", Token::Invariant);
assert_tokens!("precise", Token::Precise);
assert_tokens!("coherent", Token::Coherent);
assert_tokens!("volatile", Token::Volatile);
assert_tokens!("restrict", Token::Restrict);
assert_tokens!("readonly", Token::Readonly);
assert_tokens!("writeonly", Token::Writeonly);
assert_tokens!("common", Token::Reserved("common".into()));
assert_tokens!("partition", Token::Reserved("partition".into()));
assert_tokens!("active", Token::Reserved("active".into()));
assert_tokens!("asm", Token::Reserved("asm".into()));
assert_tokens!("class", Token::Reserved("class".into()));
assert_tokens!("union", Token::Reserved("union".into()));
assert_tokens!("enum", Token::Reserved("enum".into()));
assert_tokens!("typedef", Token::Reserved("typedef".into()));
assert_tokens!("template", Token::Reserved("template".into()));
assert_tokens!("this", Token::Reserved("this".into()));
assert_tokens!("resource", Token::Reserved("resource".into()));
assert_tokens!("goto", Token::Reserved("goto".into()));
assert_tokens!("inline", Token::Reserved("inline".into()));
assert_tokens!("noinline", Token::Reserved("noinline".into()));
assert_tokens!("public", Token::Reserved("public".into()));
assert_tokens!("static", Token::Reserved("static".into()));
assert_tokens!("extern", Token::Reserved("extern".into()));
assert_tokens!("external", Token::Reserved("external".into()));
assert_tokens!("interface", Token::Reserved("interface".into()));
assert_tokens!("long", Token::Reserved("long".into()));
assert_tokens!("short", Token::Reserved("short".into()));
assert_tokens!("half", Token::Reserved("half".into()));
assert_tokens!("fixed", Token::Reserved("fixed".into()));
assert_tokens!("unsigned", Token::Reserved("unsigned".into()));
assert_tokens!("superp", Token::Reserved("superp".into()));
assert_tokens!("input", Token::Reserved("input".into()));
assert_tokens!("output", Token::Reserved("output".into()));
assert_tokens!("hvec2", Token::Reserved("hvec2".into()));
assert_tokens!("hvec3", Token::Reserved("hvec3".into()));
assert_tokens!("hvec4", Token::Reserved("hvec4".into()));
assert_tokens!("fvec2", Token::Reserved("fvec2".into()));
assert_tokens!("fvec3", Token::Reserved("fvec3".into()));
assert_tokens!("fvec4", Token::Reserved("fvec4".into()));
assert_tokens!(
"sampler3DRect",
Token::Reserved("sampler3DRect".into())
);
assert_tokens!("filter", Token::Reserved("filter".into()));
assert_tokens!("sizeof", Token::Reserved("sizeof".into()));
assert_tokens!("cast", Token::Reserved("cast".into()));
assert_tokens!("namespace", Token::Reserved("namespace".into()));
assert_tokens!("using", Token::Reserved("using".into()));
assert_tokens!("tr\\\rue", Token::Bool(true));
assert_tokens!("dis\\\ncard", Token::Discard);
assert_tokens!("sub\\\r\nroutine", Token::Subroutine);
}
#[test]
fn punctuation() {
assert_tokens!(";", Token::Semi);
assert_tokens!(".", Token::Dot);
assert_tokens!(",", Token::Comma);
assert_tokens!("(", Token::LParen);
assert_tokens!(")", Token::RParen);
assert_tokens!("[", Token::LBracket);
assert_tokens!("]", Token::RBracket);
assert_tokens!("{", Token::LBrace);
assert_tokens!("}", Token::RBrace);
assert_tokens!(":", Token::Colon);
assert_tokens!("=", Token::Op(OpTy::Eq));
assert_tokens!("+", Token::Op(OpTy::Add));
assert_tokens!("-", Token::Op(OpTy::Sub));
assert_tokens!("*", Token::Op(OpTy::Mul));
assert_tokens!("/", Token::Op(OpTy::Div));
assert_tokens!(">", Token::Op(OpTy::Gt));
assert_tokens!("<", Token::Op(OpTy::Lt));
assert_tokens!("!", Token::Op(OpTy::Not));
assert_tokens!("~", Token::Op(OpTy::Flip));
assert_tokens!("?", Token::Question);
assert_tokens!("%", Token::Op(OpTy::Rem));
assert_tokens!("&", Token::Op(OpTy::And));
assert_tokens!("|", Token::Op(OpTy::Or));
assert_tokens!("^", Token::Op(OpTy::Xor));
assert_tokens!("==", Token::Op(OpTy::EqEq));
assert_tokens!("!=", Token::Op(OpTy::NotEq));
assert_tokens!(">=", Token::Op(OpTy::Ge));
assert_tokens!("<=", Token::Op(OpTy::Le));
assert_tokens!("&&", Token::Op(OpTy::AndAnd));
assert_tokens!("||", Token::Op(OpTy::OrOr));
assert_tokens!("^^", Token::Op(OpTy::XorXor));
assert_tokens!("++", Token::Op(OpTy::AddAdd));
assert_tokens!("--", Token::Op(OpTy::SubSub));
assert_tokens!("<<", Token::Op(OpTy::LShift));
assert_tokens!(">>", Token::Op(OpTy::RShift));
assert_tokens!("+=", Token::Op(OpTy::AddEq));
assert_tokens!("-=", Token::Op(OpTy::SubEq));
assert_tokens!("*=", Token::Op(OpTy::MulEq));
assert_tokens!("/=", Token::Op(OpTy::DivEq));
assert_tokens!("%=", Token::Op(OpTy::RemEq));
assert_tokens!("&=", Token::Op(OpTy::AndEq));
assert_tokens!("|=", Token::Op(OpTy::OrEq));
assert_tokens!("^=", Token::Op(OpTy::XorEq));
assert_tokens!("<<=", Token::Op(OpTy::LShiftEq));
assert_tokens!(">>=", Token::Op(OpTy::RShiftEq));
assert_tokens!("!\\\n=", Token::Op(OpTy::NotEq));
assert_tokens!("+\\\r=", Token::Op(OpTy::AddEq));
assert_tokens!("=\\\n=", Token::Op(OpTy::EqEq));
assert_tokens!(">>\\\r\n=", Token::Op(OpTy::RShiftEq));
}
#[test]
#[rustfmt::skip]
fn comments() {
assert_tokens!("// a comment", Token::LineComment(" a comment".into()));
assert_tokens!("//a comment", Token::LineComment("a comment".into()));
assert_tokens!("// a comment \\\rcontinuation", Token::LineComment(" a comment continuation".into()));
assert_tokens!("//a comment\\\ncontinuation", Token::LineComment("a commentcontinuation".into()));
assert_tokens!("//a comment \\\r\ncontinuation", Token::LineComment("a comment continuation".into()));
assert_tokens!("/\\\r/ a comment", Token::LineComment(" a comment".into()));
assert_tokens!("/\\\r\n/ a comment", Token::LineComment(" a comment".into()));
assert_tokens!("//\\\n a comment", Token::LineComment(" a comment".into()));
assert_tokens!("/* a comment */", Token::BlockComment{ str: " a comment ".into(), contains_eof: false});
assert_tokens!("/*a comment*/", Token::BlockComment{ str: "a comment".into(), contains_eof: false});
assert_tokens!("/* <Ll#,;#l,_!\"^$!6 */", Token::BlockComment{ str: " <Ll#,;#l,_!\"^$!6 ".into(), contains_eof: false});
assert_tokens!("/* open-ended comment", Token::BlockComment{ str: " open-ended comment".into(), contains_eof: true});
assert_tokens!("/\\\r* a comment */", Token::BlockComment{ str: " a comment ".into(), contains_eof: false});
assert_tokens!("/\\\n*a comment*\\\r\n/", Token::BlockComment{ str: "a comment".into(), contains_eof: false});
}
#[test]
#[rustfmt::skip]
fn integers(){
assert_tokens!("0", Token::Num{num: "0".into(), suffix: None, type_: NumType::Dec});
assert_tokens!("0u", Token::Num{num: "0".into(), suffix: Some("u".into()), type_: NumType::Dec});
assert_tokens!("1", Token::Num{num: "1".into(), suffix: None, type_: NumType::Dec});
assert_tokens!("123456", Token::Num{num: "123456".into(), suffix: None, type_: NumType::Dec});
assert_tokens!("100008", Token::Num{num: "100008".into(), suffix: None, type_: NumType::Dec});
assert_tokens!("1u", Token::Num{num: "1".into(), suffix: Some("u".into()), type_: NumType::Dec});
assert_tokens!("123456u", Token::Num{num: "123456".into(), suffix: Some("u".into()), type_: NumType::Dec});
assert_tokens!("100008u", Token::Num{num: "100008".into(), suffix: Some("u".into()), type_: NumType::Dec});
assert_tokens!("00", Token::Num{num: "0".into(), suffix: None, type_: NumType::Oct});
assert_tokens!("01715", Token::Num{num: "1715".into(), suffix: None, type_: NumType::Oct});
assert_tokens!("09183", Token::Num{num: "9183".into(), suffix: None, type_: NumType::Oct});
assert_tokens!("00u", Token::Num{num: "0".into(), suffix: Some("u".into()), type_: NumType::Oct});
assert_tokens!("01715u", Token::Num{num: "1715".into(), suffix: Some("u".into()), type_: NumType::Oct});
assert_tokens!("09183u", Token::Num{num: "9183".into(), suffix: Some("u".into()), type_: NumType::Oct});
assert_tokens!("0x", Token::Num{num: "".into(), suffix: None, type_: NumType::Hex});
assert_tokens!("0x91fa", Token::Num{num: "91fa".into(), suffix: None, type_: NumType::Hex});
assert_tokens!("0x00F", Token::Num{num: "00F".into(), suffix: None, type_: NumType::Hex});
assert_tokens!("0xu", Token::Num{num: "".into(), suffix: Some("u".into()), type_: NumType::Hex});
assert_tokens!("0x91fau", Token::Num{num: "91fa".into(), suffix: Some("u".into()), type_: NumType::Hex});
assert_tokens!("0x00Fu", Token::Num{num: "00F".into(), suffix: Some("u".into()), type_: NumType::Hex});
assert_tokens!("123\\\r456", Token::Num{num: "123456".into(), suffix: None, type_: NumType::Dec});
assert_tokens!("12\\\n3456u", Token::Num{num: "123456".into(), suffix: Some("u".into()), type_: NumType::Dec});
assert_tokens!("0171\\\n5", Token::Num{num: "1715".into(), suffix: None, type_: NumType::Oct});
assert_tokens!("0x91\\\r\nfa", Token::Num{num: "91fa".into(), suffix: None, type_: NumType::Hex});
assert_tokens!("0x\\\r91fau", Token::Num{num: "91fa".into(), suffix: Some("u".into()), type_: NumType::Hex});
assert_tokens!("0x\\\nu", Token::Num{num: "".into(), suffix: Some("u".into()), type_: NumType::Hex});
}
#[test]
#[rustfmt::skip]
fn floats() {
assert_tokens!("0.0", Token::Num{num: "0.0".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0.", Token::Num{num: "0.".into(), suffix: None, type_: NumType::Float});
assert_tokens!(".0", Token::Num{num: ".0".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0.0lf", Token::Num{num: "0.0".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0.lf", Token::Num{num: "0.".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!(".0lf", Token::Num{num: ".0".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0e7", Token::Num{num: "0e7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0e+7", Token::Num{num: "0e+7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0e-7", Token::Num{num: "0e-7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0.0e7", Token::Num{num: "0.0e7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0.0e+7", Token::Num{num: "0.0e+7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0.0e-7", Token::Num{num: "0.0e-7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0.e7", Token::Num{num: "0.e7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0.e+7", Token::Num{num: "0.e+7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0.e-7", Token::Num{num: "0.e-7".into(), suffix: None, type_: NumType::Float});
assert_tokens!(".0e7", Token::Num{num: ".0e7".into(), suffix: None, type_: NumType::Float});
assert_tokens!(".0e+7", Token::Num{num: ".0e+7".into(), suffix: None, type_: NumType::Float});
assert_tokens!(".0e-7", Token::Num{num: ".0e-7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0e7lf", Token::Num{num: "0e7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0e+7lf", Token::Num{num: "0e+7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0e-7lf", Token::Num{num: "0e-7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0.0e7lf", Token::Num{num: "0.0e7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0.0e+7lf", Token::Num{num: "0.0e+7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0.0e-7lf", Token::Num{num: "0.0e-7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0.e7lf", Token::Num{num: "0.e7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0.e+7lf", Token::Num{num: "0.e+7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0.e-7lf", Token::Num{num: "0.e-7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!(".0e7lf", Token::Num{num: ".0e7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!(".0e+7lf", Token::Num{num: ".0e+7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!(".0e-7lf", Token::Num{num: ".0e-7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.0", Token::Num{num: "1.0".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.1", Token::Num{num: "1.1".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.", Token::Num{num: "1.".into(), suffix: None, type_: NumType::Float});
assert_tokens!(".1", Token::Num{num: ".1".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.0lf", Token::Num{num: "1.0".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.1lf", Token::Num{num: "1.1".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.lf", Token::Num{num: "1.".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!(".1lf", Token::Num{num: ".1".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1e7", Token::Num{num: "1e7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1e+7", Token::Num{num: "1e+7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1e-7", Token::Num{num: "1e-7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.0e7", Token::Num{num: "1.0e7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.0e+7", Token::Num{num: "1.0e+7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.0e-7", Token::Num{num: "1.0e-7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.1e7", Token::Num{num: "1.1e7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.1e+7", Token::Num{num: "1.1e+7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.1e-7", Token::Num{num: "1.1e-7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.e7", Token::Num{num: "1.e7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.e+7", Token::Num{num: "1.e+7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.e-7", Token::Num{num: "1.e-7".into(), suffix: None, type_: NumType::Float});
assert_tokens!(".1e7", Token::Num{num: ".1e7".into(), suffix: None, type_: NumType::Float});
assert_tokens!(".1e+7", Token::Num{num: ".1e+7".into(), suffix: None, type_: NumType::Float});
assert_tokens!(".1e-7", Token::Num{num: ".1e-7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1e7lf", Token::Num{num: "1e7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1e+7lf", Token::Num{num: "1e+7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1e-7lf", Token::Num{num: "1e-7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.0e7lf", Token::Num{num: "1.0e7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.0e+7lf", Token::Num{num: "1.0e+7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.0e-7lf", Token::Num{num: "1.0e-7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.1e7lf", Token::Num{num: "1.1e7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.1e+7lf", Token::Num{num: "1.1e+7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.1e-7lf", Token::Num{num: "1.1e-7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.e7lf", Token::Num{num: "1.e7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.e+7lf", Token::Num{num: "1.e+7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("1.e-7lf", Token::Num{num: "1.e-7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!(".1e7lf", Token::Num{num: ".1e7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!(".1e+7lf", Token::Num{num: ".1e+7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!(".1e-7lf", Token::Num{num: ".1e-7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0.\\\r0", Token::Num{num: "0.0".into(), suffix: None, type_: NumType::Float});
assert_tokens!(".\\\n0", Token::Num{num: ".0".into(), suffix: None, type_: NumType::Float});
assert_tokens!(".0\\\nlf", Token::Num{num: ".0".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0.\\\r\nlf", Token::Num{num: "0.".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!("0e\\\r7", Token::Num{num: "0e7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("0e\\\r\n-7", Token::Num{num: "0e-7".into(), suffix: None, type_: NumType::Float});
assert_tokens!(".0\\\r\ne+7", Token::Num{num: ".0e+7".into(), suffix: None, type_: NumType::Float});
assert_tokens!("1.0e-\\\n7lf", Token::Num{num: "1.0e-7".into(), suffix: Some("lf".into()), type_: NumType::Float});
assert_tokens!(".1\\\re-7lf", Token::Num{num: ".1e-7".into(), suffix: Some("lf".into()), type_: NumType::Float});
}
#[test]
#[rustfmt::skip]
fn illegal(){
assert_tokens!("@", Token::Invalid('@'));
assert_tokens!("¬", Token::Invalid('¬'));
assert_tokens!("`", Token::Invalid('`'));
assert_tokens!("¦", Token::Invalid('¦'));
assert_tokens!("'", Token::Invalid('\''));
assert_tokens!("\"", Token::Invalid('"'));
assert_tokens!("£", Token::Invalid('£'));
assert_tokens!("$", Token::Invalid('$'));
assert_tokens!("€", Token::Invalid('€'));
}
#[cfg(test)]
mod preproc {
use super::super::{
preprocessor::{
ConditionToken, DefineToken, ExtensionToken, LineToken,
TokenStream, UndefToken, VersionToken,
},
Token,
};
use crate::span;
#[test]
fn empty() {
assert_tokens2!(
"#",
(Token::Directive(TokenStream::Empty), span(0, 1))
);
assert_tokens2!(
"# ",
(Token::Directive(TokenStream::Empty), span(0, 5))
);
}
#[test]
fn custom() {
assert_tokens2!(
"#custom",
(
Token::Directive(TokenStream::Custom {
kw: ("custom".into(), span(1, 7)),
content: None
}),
span(0, 7)
)
);
assert_tokens2!(
"# custom ",
(
Token::Directive(TokenStream::Custom {
kw: ("custom".into(), span(2, 8)),
content: Some((" ".into(), span(8, 14)))
}),
span(0, 14)
)
);
assert_tokens2!(
"#custom foobar 5 @;#",
(
Token::Directive(TokenStream::Custom {
kw: ("custom".into(), span(1, 7)),
content: Some((" foobar 5 @;#".into(), span(7, 20)))
}),
span(0, 20)
)
);
assert_tokens2!(
"# custom-5 bar",
(
Token::Directive(TokenStream::Custom {
kw: ("custom".into(), span(2, 8)),
content: Some(("-5 bar".into(), span(8, 14))),
}),
span(0, 14)
)
);
}
#[test]
fn invalid() {
assert_tokens2!(
"# # 55 @ `!",
(
Token::Directive(TokenStream::Invalid {
content: ("# 55 @ `!".into(), span(2, 11))
}),
span(0, 11)
)
);
}
#[test]
fn version() {
assert_tokens2!(
"#version",
(
Token::Directive(TokenStream::Version {
kw: span(1, 8),
tokens: vec![]
}),
span(0, 8)
)
);
assert_tokens2!(
"#version 450 core",
(
Token::Directive(TokenStream::Version {
kw: span(1, 8),
tokens: vec![
(VersionToken::Num(450), span(9, 12)),
(VersionToken::Word("core".into()), span(13, 17)),
]
}),
span(0, 17)
)
);
assert_tokens2!(
"# version 330 es",
(
Token::Directive(TokenStream::Version {
kw: span(4, 11),
tokens: vec![
(VersionToken::Num(330), span(12, 15)),
(VersionToken::Word("es".into()), span(16, 18)),
]
}),
span(0, 18)
)
);
assert_tokens2!(
"#version foobar ",
(
Token::Directive(TokenStream::Version {
kw: span(1, 8),
tokens: vec![(
VersionToken::Word("foobar".into()),
span(9, 15)
)]
}),
span(0, 20)
)
);
assert_tokens2!(
"# version 100compatability ##@;",
(
Token::Directive(TokenStream::Version {
kw: span(2, 9),
tokens: vec![
(
VersionToken::InvalidNum(
"100compatability".into()
),
span(10, 26)
),
(VersionToken::Invalid('#'), span(27, 28)),
(VersionToken::Invalid('#'), span(28, 29)),
(VersionToken::Invalid('@'), span(29, 30)),
(VersionToken::Invalid(';'), span(30, 31))
]
}),
span(0, 31)
)
);
}
#[test]
fn extension() {
assert_tokens2!(
"#extension",
(
Token::Directive(TokenStream::Extension {
kw: span(1, 10),
tokens: vec![]
}),
span(0, 10)
)
);
assert_tokens2!(
"# extension foobar : enable",
(
Token::Directive(TokenStream::Extension {
kw: span(3, 12),
tokens: vec![
(
ExtensionToken::Word("foobar".into()),
span(13, 19)
),
(ExtensionToken::Colon, span(20, 21)),
(
ExtensionToken::Word("enable".into()),
span(22, 28)
)
]
}),
span(0, 28)
)
);
assert_tokens2!(
"#extension: 600 ",
(
Token::Directive(TokenStream::Extension {
kw: span(1, 10),
tokens: vec![
(ExtensionToken::Colon, span(10, 11)),
(ExtensionToken::Invalid('6'), span(12, 13)),
(ExtensionToken::Invalid('0'), span(13, 14)),
(ExtensionToken::Invalid('0'), span(14, 15))
]
}),
span(0, 18)
)
);
}
#[test]
fn line() {
assert_tokens2!(
"#line",
(
Token::Directive(TokenStream::Line {
kw: span(1, 5),
tokens: vec![]
}),
span(0, 5)
)
);
assert_tokens2!(
"# line 5 1007",
(
Token::Directive(TokenStream::Line {
kw: span(2, 6),
tokens: vec![
(LineToken::Num(5), span(7, 8)),
(LineToken::Num(1007), span(9, 13))
]
}),
span(0, 13)
)
);
assert_tokens2!(
"#line FOO",
(
Token::Directive(TokenStream::Line {
kw: span(1, 5),
tokens: vec![(
LineToken::Ident("FOO".into()),
span(6, 9)
)]
}),
span(0, 9)
)
);
assert_tokens2!(
"# line 734abc ",
(
Token::Directive(TokenStream::Line {
kw: span(3, 7),
tokens: vec![(
LineToken::InvalidNum("734abc".into()),
span(9, 15)
)]
}),
span(0, 20)
)
);
}
#[test]
fn define() {
use super::{NumType, OpTy};
assert_tokens2!(
"#define",
(
Token::Directive(TokenStream::Define {
kw: span(1, 7),
ident_tokens: vec![],
body_tokens: vec![],
}),
span(0, 7)
)
);
assert_tokens2!(
"#define foobar",
(
Token::Directive(TokenStream::Define {
kw: span(1, 7),
ident_tokens: vec![(
DefineToken::Ident("foobar".into()),
span(8, 14)
)],
body_tokens: vec![],
}),
span(0, 14)
)
);
assert_tokens2!(
"# define FOO 5 ",
(
Token::Directive(TokenStream::Define {
kw: span(3, 9),
ident_tokens: vec![(
DefineToken::Ident("FOO".into()),
span(10, 13)
)],
body_tokens: vec![(
Token::Num {
type_: NumType::Dec,
num: "5".into(),
suffix: None
},
span(14, 15)
)]
}),
span(0, 18)
)
);
assert_tokens2!(
"#define FOO_5 if [bar##0x6}",
(
Token::Directive(TokenStream::Define {
kw: span(1, 7),
ident_tokens: vec![(
DefineToken::Ident("FOO_5".into()),
span(8, 13)
)],
body_tokens: vec![
(Token::If, span(15, 17)),
(Token::LBracket, span(18, 19)),
(Token::Ident("bar".into()), span(19, 22)),
(Token::MacroConcat, span(22, 24)),
(
Token::Num {
type_: NumType::Hex,
num: "6".into(),
suffix: None
},
span(24, 27)
),
(Token::RBrace, span(27, 28))
]
}),
span(0, 28)
)
);
assert_tokens2!(
"#define baz ( )",
(
Token::Directive(TokenStream::Define {
kw: span(1, 7),
ident_tokens: vec![(
DefineToken::Ident("baz".into()),
span(8, 11)
),],
body_tokens: vec![
(Token::LParen, span(12, 13)),
(Token::RParen, span(14, 15))
]
}),
span(0, 15)
)
);
assert_tokens2!(
"#define 5 @@ ` ",
(
Token::Directive(TokenStream::Define {
kw: span(1, 7),
ident_tokens: vec![],
body_tokens: vec![
(
Token::Num {
type_: NumType::Dec,
num: "5".into(),
suffix: None
},
span(8, 9)
),
(Token::Invalid('@'), span(10, 11)),
(Token::Invalid('@'), span(11, 12)),
(Token::Invalid('`'), span(13, 14)),
]
}),
span(0, 15)
)
);
assert_tokens2!(
"#define FOOBAR()",
(
Token::Directive(TokenStream::Define {
kw: span(1, 7),
ident_tokens: vec![
(DefineToken::Ident("FOOBAR".into()), span(8, 14)),
(DefineToken::LParen, span(14, 15)),
(DefineToken::RParen, span(15, 16)),
],
body_tokens: vec![]
}),
span(0, 16)
)
);
assert_tokens2!(
"#define baz( )",
(
Token::Directive(TokenStream::Define {
kw: span(1, 7),
ident_tokens: vec![
(DefineToken::Ident("baz".into()), span(8, 11)),
(DefineToken::LParen, span(11, 12)),
(DefineToken::RParen, span(13, 14))
],
body_tokens: vec![]
}),
span(0, 14)
)
);
assert_tokens2!(
"#define FOOBAR( a, b)",
(
Token::Directive(TokenStream::Define {
kw: span(1, 7),
ident_tokens: vec![
(DefineToken::Ident("FOOBAR".into()), span(8, 14)),
(DefineToken::LParen, span(14, 15)),
(DefineToken::Ident("a".into()), span(16, 17)),
(DefineToken::Comma, span(17, 18)),
(DefineToken::Ident("b".into()), span(19, 20)),
(DefineToken::RParen, span(20, 21)),
],
body_tokens: vec![]
}),
span(0, 21)
)
);
assert_tokens2!(
"#define FOOBAR( a # @@",
(
Token::Directive(TokenStream::Define {
kw: span(1, 7),
ident_tokens: vec![
(DefineToken::Ident("FOOBAR".into()), span(8, 14)),
(DefineToken::LParen, span(14, 15)),
(DefineToken::Ident("a".into()), span(16, 17)),
(DefineToken::Invalid('#'), span(18, 19)),
(DefineToken::Invalid('@'), span(20, 21)),
(DefineToken::Invalid('@'), span(21, 22)),
],
body_tokens: vec![]
}),
span(0, 22)
)
);
assert_tokens2!(
"#define FOOBAR( a) if [0x7u## %!",
(
Token::Directive(TokenStream::Define {
kw: span(1, 7),
ident_tokens: vec![
(DefineToken::Ident("FOOBAR".into()), span(8, 14)),
(DefineToken::LParen, span(14, 15)),
(DefineToken::Ident("a".into()), span(16, 17)),
(DefineToken::RParen, span(17, 18)),
],
body_tokens: vec![
(Token::If, span(20, 22)),
(Token::LBracket, span(23, 24)),
(
Token::Num {
type_: NumType::Hex,
num: "7".into(),
suffix: Some("u".into())
},
span(24, 28)
),
(Token::MacroConcat, span(28, 30)),
(Token::Op(OpTy::Rem), span(31, 32)),
(Token::Op(OpTy::Not), span(32, 33)),
]
}),
span(0, 33)
)
);
}
#[test]
fn undef() {
assert_tokens2!(
"#undef",
(
Token::Directive(TokenStream::Undef {
kw: span(1, 6),
tokens: vec![]
}),
span(0, 6)
)
);
assert_tokens2!(
"# undef foo ",
(
Token::Directive(TokenStream::Undef {
kw: span(2, 7),
tokens: vec![(
UndefToken::Ident("foo".into()),
span(8, 11)
)]
}),
span(0, 12)
)
);
assert_tokens2!(
"# undef foobar @ `` 4 ",
(
Token::Directive(TokenStream::Undef {
kw: span(5, 10),
tokens: vec![
(UndefToken::Ident("foobar".into()), span(11, 17)),
(UndefToken::Invalid('@'), span(18, 19)),
(UndefToken::Invalid('`'), span(20, 21)),
(UndefToken::Invalid('`'), span(21, 22)),
(UndefToken::Invalid('4'), span(23, 24)),
]
}),
span(0, 28)
)
);
}
#[test]
fn conditional() {
assert_tokens2!(
"#if",
(
Token::Directive(TokenStream::If {
kw: span(1, 3),
tokens: vec![]
}),
span(0, 3)
)
);
assert_tokens2!(
"# if FOO > 5",
(
Token::Directive(TokenStream::If {
kw: span(2, 4),
tokens: vec![
(ConditionToken::Ident("FOO".into()), span(5, 8)),
(ConditionToken::Gt, span(9, 10)),
(ConditionToken::Num(5), span(11, 12))
]
}),
span(0, 12)
)
);
assert_tokens2!(
"#if 5001bar",
(
Token::Directive(TokenStream::If {
kw: span(1, 3),
tokens: vec![(
ConditionToken::InvalidNum("5001bar".into()),
span(4, 11)
)]
}),
span(0, 11)
)
);
assert_tokens2!(
"#if (defined foobar) && 5 <8",
(
Token::Directive(TokenStream::If {
kw: span(1, 3),
tokens: vec![
(ConditionToken::LParen, span(4, 5)),
(ConditionToken::Defined, span(5, 12)),
(
ConditionToken::Ident("foobar".into()),
span(13, 19)
),
(ConditionToken::RParen, span(19, 20)),
(ConditionToken::AndAnd, span(21, 23)),
(ConditionToken::Num(5), span(24, 25)),
(ConditionToken::Lt, span(26, 27)),
(ConditionToken::Num(8), span(27, 28))
]
}),
span(0, 28)
)
);
assert_tokens2!(
"#if baz @ ## : ",
(
Token::Directive(TokenStream::If {
kw: span(1, 3),
tokens: vec![
(ConditionToken::Ident("baz".into()), span(4, 7)),
(ConditionToken::Invalid('@'), span(8, 9)),
(ConditionToken::Invalid('#'), span(10, 11)),
(ConditionToken::Invalid('#'), span(11, 12)),
(ConditionToken::Invalid(':'), span(13, 14)),
]
}),
span(0, 17)
)
);
}
#[test]
fn error() {
assert_tokens2!(
"#error",
(
Token::Directive(TokenStream::Error {
kw: span(1, 6),
message: None
}),
span(0, 6)
)
);
assert_tokens2!(
"# error foo bar ## @ ; ",
(
Token::Directive(TokenStream::Error {
kw: span(2, 7),
message: Some((
" foo bar ## @ ; ".into(),
span(7, 28)
))
}),
span(0, 28)
)
);
}
#[test]
fn pragma() {
assert_tokens2!(
"#pragma",
(
Token::Directive(TokenStream::Pragma {
kw: span(1, 7),
options: None
}),
span(0, 7)
)
);
assert_tokens2!(
"# pragma foo bar ## @ ; ",
(
Token::Directive(TokenStream::Pragma {
kw: span(2, 8),
options: Some((
" foo bar ## @ ; ".into(),
span(8, 29)
))
}),
span(0, 29)
)
);
}
}
}