use super::token::*;
use super::CLangStandard;
#[derive(Debug, Clone, Default)]
pub struct SourceLocation {
pub file: String,
pub line: u32,
pub column: u32,
}
pub struct SourceManager {
pub files: std::collections::HashMap<String, String>,
}
pub struct Lexer<'a> {
source: &'a str,
pos: usize,
line: u32,
column: u32,
tokens: Vec<Token>,
errors: Vec<String>,
standard: CLangStandard,
at_start_of_line: bool,
has_leading_space: bool,
}
impl<'a> Lexer<'a> {
pub fn replace_trigraphs(source: &str) -> String {
let mut result = String::with_capacity(source.len());
let chars: Vec<char> = source.chars().collect();
let mut i = 0;
while i < chars.len() {
if i + 2 < chars.len() && chars[i] == '?' && chars[i + 1] == '?' {
let replacement = match chars[i + 2] {
'=' => Some('#'),
'(' => Some('['),
'/' => Some('\\'),
')' => Some(']'),
'\'' => Some('^'),
'<' => Some('{'),
'!' => Some('|'),
'>' => Some('}'),
'-' => Some('~'),
_ => None,
};
if let Some(ch) = replacement {
result.push(ch);
i += 3;
continue;
}
}
result.push(chars[i]);
i += 1;
}
result
}
pub fn try_lex_digraph(&self, pos: usize) -> Option<(TokenKind, usize)> {
let chars: Vec<char> = self.source[pos..].chars().collect();
if chars.len() < 2 {
return None;
}
match (chars[0], chars[1]) {
('<', ':') => Some((TokenKind::LBracket, 2)),
(':', '>') => Some((TokenKind::RBracket, 2)),
('<', '%') => Some((TokenKind::LBrace, 2)),
('%', '>') => Some((TokenKind::RBrace, 2)),
('%', ':') => {
let chars: Vec<char> = self.source[pos..].chars().collect();
if chars.len() >= 4 && chars[2] == '%' && chars[3] == ':' {
Some((TokenKind::HashHash, 4))
} else {
Some((TokenKind::Hash, 2))
}
}
_ => None,
}
}
pub fn is_valid_ucn(codepoint: u32, is_short_form: bool) -> bool {
if codepoint > 0x10FFFF {
return false;
}
if (0xD800..=0xDFFF).contains(&codepoint) {
return false;
}
if is_short_form && codepoint > 0xFFFF {
return false;
}
if codepoint < 0xA0 {
if codepoint != 0x24 && codepoint != 0x40 && codepoint != 0x60 {
return false;
}
}
true
}
pub fn parse_ucn(source: &str, pos: usize) -> Option<(u32, usize)> {
let chars: Vec<char> = source[pos..].chars().collect();
if chars.len() < 2 || chars[0] != '\\' {
return None;
}
match chars.get(1) {
Some(&'u') => {
let mut val: u32 = 0;
let mut count = 0;
for i in 2..6.min(chars.len()) {
if let Some(d) = chars[i].to_digit(16) {
val = val * 16 + d;
count += 1;
} else {
break;
}
}
if count == 4 && Lexer::is_valid_ucn(val, true) {
Some((val, 2 + count))
} else {
None
}
}
Some(&'U') => {
let mut val: u32 = 0;
let mut count = 0;
for i in 2..10.min(chars.len()) {
if let Some(d) = chars[i].to_digit(16) {
val = val * 16 + d;
count += 1;
} else {
break;
}
}
if count == 8 && Lexer::is_valid_ucn(val, false) {
Some((val, 2 + count))
} else {
None
}
}
_ => None,
}
}
pub fn decode_escape_full(chars: &mut impl Iterator<Item = char>) -> Result<char, String> {
match chars.next() {
None => Err("unterminated escape sequence".to_string()),
Some('\\') => Ok('\\'),
Some('\'') => Ok('\''),
Some('"') => Ok('"'),
Some('?') => Ok('?'),
Some('a') => Ok('\x07'),
Some('b') => Ok('\x08'),
Some('f') => Ok('\x0C'),
Some('n') => Ok('\n'),
Some('r') => Ok('\r'),
Some('t') => Ok('\t'),
Some('v') => Ok('\x0B'),
Some('e') => Ok('\x1B'), Some('0'..='7') => {
Err("use decode_escape".to_string())
}
Some('x') => {
Err("use decode_escape".to_string())
}
Some('u') => Err("use decode_escape".to_string()),
Some('U') => Err("use decode_escape".to_string()),
Some(_c) => Err(format!("unknown escape sequence: '\\\\{}'", '?')),
}
}
pub fn parse_integer_suffixes(suffix: &str) -> (bool, bool, bool) {
let mut is_unsigned = false;
let mut is_long = false;
let mut is_long_long = false;
let s = suffix.to_lowercase();
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'u' => {
if is_unsigned {
break;
}
is_unsigned = true;
i += 1;
}
b'l' => {
if i + 1 < bytes.len() && bytes[i + 1] == b'l' {
if is_long_long {
break;
}
is_long_long = true;
is_long = false;
i += 2;
} else {
if is_long || is_long_long {
break;
}
is_long = true;
i += 1;
}
}
_ => break, }
}
(is_unsigned, is_long, is_long_long)
}
pub fn lex_integer_literal(&self, pos: usize) -> Option<(TokenKind, usize, String)> {
let chars: Vec<char> = self.source[pos..].chars().collect();
if chars.is_empty() {
return None;
}
let mut i = 0;
let mut radix: u32 = 10;
let mut text = String::new();
if chars[0] == '0' && chars.len() > 1 {
match chars[1] {
'x' | 'X' => {
radix = 16;
text.push(chars[0]);
text.push(chars[1]);
i = 2;
}
'b' | 'B' => {
radix = 2;
text.push(chars[0]);
text.push(chars[1]);
i = 2;
}
_ => {
radix = 8;
text.push(chars[0]);
i = 1;
}
}
}
while i < chars.len() {
match chars[i] {
'0'..='1' if radix == 2 => {
text.push(chars[i]);
i += 1;
}
'0'..='7' if radix == 8 => {
text.push(chars[i]);
i += 1;
}
'0'..='9' if radix == 10 || radix == 16 => {
text.push(chars[i]);
i += 1;
}
'a'..='f' | 'A'..='F' if radix == 16 => {
text.push(chars[i]);
i += 1;
}
_ => break,
}
}
let suffix_start = i;
while i < chars.len() {
match chars[i] {
'u' | 'U' | 'l' | 'L' => {
text.push(chars[i]);
i += 1;
}
_ => break,
}
}
let suffix_text: String = chars[suffix_start..i].iter().collect();
let (_unsigned, _long, _long_long) = Self::parse_integer_suffixes(&suffix_text);
Some((TokenKind::NumericLiteral, i, text))
}
pub fn lex_float_literal(&self, pos: usize) -> Option<(TokenKind, usize, String)> {
let chars: Vec<char> = self.source[pos..].chars().collect();
if chars.is_empty() {
return None;
}
let mut i = 0;
let mut is_hex = false;
let mut text = String::new();
if chars[0] == '0' && chars.len() > 1 && (chars[1] == 'x' || chars[1] == 'X') {
text.push(chars[0]);
text.push(chars[1]);
i = 2;
}
if i >= 2 {
let mut j = i;
while j < chars.len() && chars[j].is_ascii_hexdigit() {
j += 1;
}
if j < chars.len() && chars[j] == '.' {
is_hex = true;
} else {
let mut k = j;
while k < chars.len() && chars[k].is_ascii_hexdigit() {
k += 1;
}
if k < chars.len() && (chars[k] == 'p' || chars[k] == 'P') {
is_hex = true;
}
}
}
while i < chars.len() {
let valid = if is_hex {
chars[i].is_ascii_hexdigit()
} else {
chars[i].is_ascii_digit()
};
if valid {
text.push(chars[i]);
i += 1;
} else {
break;
}
}
if i < chars.len() && chars[i] == '.' {
text.push(chars[i]);
i += 1;
while i < chars.len() {
let valid = if is_hex {
chars[i].is_ascii_hexdigit()
} else {
chars[i].is_ascii_digit()
};
if valid {
text.push(chars[i]);
i += 1;
} else {
break;
}
}
}
if i < chars.len() {
let exp_char = if is_hex { 'p' } else { 'e' };
if chars[i].eq_ignore_ascii_case(&exp_char) {
text.push(chars[i]);
i += 1;
if i < chars.len() && (chars[i] == '+' || chars[i] == '-') {
text.push(chars[i]);
i += 1;
}
while i < chars.len() && chars[i].is_ascii_digit() {
text.push(chars[i]);
i += 1;
}
}
}
while i < chars.len() {
match chars[i] {
'f' | 'F' | 'l' | 'L' => {
text.push(chars[i]);
i += 1;
}
_ => break,
}
}
Some((TokenKind::NumericLiteral, i, text))
}
pub fn lex_char_literal_full(&self, pos: usize) -> Option<(TokenKind, usize, String)> {
let chars: Vec<char> = self.source[pos..].chars().collect();
if chars.is_empty() {
return None;
}
let mut i = 0;
let mut kind = TokenKind::CharLiteral;
let mut text = String::new();
match chars[0] {
'L' => {
if chars.len() > 1 && chars[1] == '\'' {
kind = TokenKind::WideCharLiteral;
text.push('L');
i = 1;
}
}
'u' => {
if chars.len() > 2 && chars[1] == '8' && chars[2] == '\'' {
kind = TokenKind::UTF8CharLiteral;
text.push_str("u8");
i = 2;
} else if chars.len() > 1 && chars[1] == '\'' {
kind = TokenKind::UTF16CharLiteral;
text.push('u');
i = 1;
}
}
'U' => {
if chars.len() > 1 && chars[1] == '\'' {
kind = TokenKind::UTF32CharLiteral;
text.push('U');
i = 1;
}
}
_ => {}
}
if i >= chars.len() || chars[i] != '\'' {
return None;
}
text.push(chars[i]);
i += 1;
let mut found_closing = false;
while i < chars.len() {
if chars[i] == '\'' {
text.push(chars[i]);
i += 1;
found_closing = true;
break;
}
if chars[i] == '\\' && i + 1 < chars.len() {
text.push(chars[i]); i += 1;
text.push(chars[i]); i += 1;
} else if chars[i] == '\n' {
break;
} else {
text.push(chars[i]);
i += 1;
}
}
if !found_closing {
return None;
}
Some((kind, i, text))
}
pub fn lex_string_literal_full(&self, pos: usize) -> Option<(TokenKind, usize, String)> {
let chars: Vec<char> = self.source[pos..].chars().collect();
if chars.is_empty() {
return None;
}
let mut i = 0;
let mut kind = TokenKind::StringLiteral;
let mut text = String::new();
match chars[0] {
'L' => {
if chars.len() > 1 && chars[1] == '"' {
kind = TokenKind::WideStringLiteral;
text.push('L');
i = 1;
}
}
'u' => {
if chars.len() > 2 && chars[1] == '8' && chars[2] == '"' {
kind = TokenKind::UTF8StringLiteral;
text.push_str("u8");
i = 2;
} else if chars.len() > 1 && chars[1] == '"' {
kind = TokenKind::UTF16StringLiteral;
text.push('u');
i = 1;
}
}
'U' => {
if chars.len() > 1 && chars[1] == '"' {
kind = TokenKind::UTF32StringLiteral;
text.push('U');
i = 1;
}
}
_ => {}
}
if i >= chars.len() || chars[i] != '"' {
return None;
}
text.push(chars[i]);
i += 1;
let mut found_closing = false;
while i < chars.len() {
if chars[i] == '"' {
text.push(chars[i]);
i += 1;
found_closing = true;
break;
}
if chars[i] == '\\' && i + 1 < chars.len() {
text.push(chars[i]);
i += 1;
text.push(chars[i]);
i += 1;
} else if chars[i] == '\n' {
break;
} else {
text.push(chars[i]);
i += 1;
}
}
if !found_closing {
return None;
}
Some((kind, i, text))
}
pub fn lex_raw_string_literal(&self, pos: usize) -> Option<(TokenKind, usize, String)> {
let chars: Vec<char> = self.source[pos..].chars().collect();
if chars.len() < 4 {
return None;
}
if chars[0] != 'R' || chars[1] != '"' {
return None;
}
let mut i = 2;
let mut text = String::from("R\"");
let delimiter_start = i;
while i < chars.len() && chars[i] != '(' {
if chars[i] == ')' || chars[i] == '\\' || chars[i].is_whitespace() {
return None;
}
text.push(chars[i]);
i += 1;
}
if i >= chars.len() {
return None;
}
let delimiter: String = chars[delimiter_start..i].iter().collect();
text.push(chars[i]); i += 1;
let mut raw_content = String::new();
while i < chars.len() {
if chars[i] == ')' {
let mut j = i + 1;
let mut matched = true;
for d in delimiter.chars() {
if j >= chars.len() || chars[j] != d {
matched = false;
break;
}
j += 1;
}
if matched && j < chars.len() && chars[j] == '"' {
raw_content.push(')');
raw_content.push_str(&delimiter);
raw_content.push('"');
text.push_str(&raw_content);
i = j + 1;
return Some((TokenKind::RawStringLiteral, i, text));
}
}
raw_content.push(chars[i]);
text.push(chars[i]);
i += 1;
}
None
}
pub fn current_location(&self) -> (u32, u32, usize) {
(self.line, self.column, self.pos)
}
pub fn location_at(&self, pos: usize) -> (u32, u32) {
let mut line = 1u32;
let mut col = 1u32;
let mut i = 0;
for ch in self.source.chars() {
if i >= pos {
break;
}
if ch == '\n' {
line += 1;
col = 1;
} else {
col += 1;
}
i += ch.len_utf8();
}
(line, col)
}
pub fn can_concatenate(&self, tok1: &Token, tok2: &Token) -> bool {
matches!(
(&tok1.kind, &tok2.kind),
(TokenKind::Identifier, TokenKind::Identifier)
| (TokenKind::Identifier, TokenKind::NumericLiteral)
| (TokenKind::NumericLiteral, TokenKind::Identifier)
| (TokenKind::NumericLiteral, TokenKind::NumericLiteral)
)
}
}
pub fn apply_trigraphs(source: &str) -> String {
Lexer::replace_trigraphs(source)
}
pub fn has_trigraphs(source: &str) -> bool {
let chars: Vec<char> = source.chars().collect();
for i in 0..chars.len().saturating_sub(2) {
if chars[i] == '?' && chars[i + 1] == '?' {
match chars.get(i + 2) {
Some('=' | '(' | '/' | ')' | '\'' | '<' | '!' | '>' | '-') => return true,
_ => {}
}
}
}
false
}
pub fn decode_c_escape(seq: &str) -> Result<char, String> {
let mut chars = seq.chars();
if chars.next() != Some('\\') {
return Err("not an escape sequence".to_string());
}
Lexer::decode_escape_full(&mut chars)
}
impl<'a> Lexer<'a> {
pub fn new(source: &'a str, standard: CLangStandard) -> Self {
Self {
source,
pos: 0,
line: 1,
column: 1,
tokens: Vec::new(),
errors: Vec::new(),
standard,
at_start_of_line: true,
has_leading_space: false,
}
}
pub fn lex_all(&mut self) -> &[Token] {
loop {
let token = self.next_token();
let is_eof = token.is_eof();
self.tokens.push(token);
if is_eof {
break;
}
}
&self.tokens
}
pub fn peek_token(&self) -> Option<Token> {
let mut snapshot = Lexer {
source: self.source,
pos: self.pos,
line: self.line,
column: self.column,
tokens: Vec::new(),
errors: Vec::new(),
standard: self.standard,
at_start_of_line: self.at_start_of_line,
has_leading_space: self.has_leading_space,
};
let tok = snapshot.next_token();
if tok.is_eof() {
None
} else {
Some(tok)
}
}
pub fn next_token(&mut self) -> Token {
self.skip_whitespace_and_comments();
let start = self.pos;
let loc = SourceLoc::new(self.line, self.column, start);
let ch = match self.cur() {
Some(c) => c,
None => return Token::eof(loc),
};
if is_ident_start(ch) {
return self.lex_identifier_or_keyword(loc);
}
if ch.is_ascii_digit() {
return self.lex_number(loc);
}
if ch == '.' {
if let Some(next) = self.peek(1) {
if next.is_ascii_digit() {
return self.lex_number(loc);
}
}
}
if ch == '\'' {
return self.lex_char_or_string_literal(loc, '\'');
}
if ch == '"' {
return self.lex_char_or_string_literal(loc, '"');
}
if let Some(tok) = self.lex_punctuator(loc, ch) {
return tok;
}
if ch == '\n' {
self.advance();
self.line += 1;
self.column = 1;
self.at_start_of_line = true;
return Token::new(TokenKind::Newline, "\n", loc);
}
if ch == '\r' {
self.advance();
if self.cur() == Some('\n') {
self.advance();
}
self.line += 1;
self.column = 1;
self.at_start_of_line = true;
return Token::new(TokenKind::Newline, "\n", loc);
}
self.advance();
self.errors.push(format!(
"{}:{}: unknown character '{}' (0x{:02X})",
loc.line, loc.column, ch, ch as u32
));
let text = &self.source[start..self.pos];
Token::unknown(text, loc)
}
pub fn errors(&self) -> &[String] {
&self.errors
}
pub fn error_count(&self) -> usize {
self.errors.len()
}
#[inline]
fn cur(&self) -> Option<char> {
self.source[self.pos..].chars().next()
}
#[inline]
fn peek(&self, offset: usize) -> Option<char> {
self.source[self.pos..].chars().nth(offset)
}
#[inline]
fn advance(&mut self) {
if let Some(c) = self.cur() {
self.pos += c.len_utf8();
self.column += 1;
}
}
fn advance_by(&mut self, n: usize) {
for _ in 0..n {
self.advance();
}
}
fn matches_literal(&self, s: &str) -> bool {
self.source[self.pos..].starts_with(s)
}
fn skip_whitespace_and_comments(&mut self) {
self.has_leading_space = false;
loop {
match self.cur() {
Some(' ') | Some('\t') | Some('\x0C') => {
self.has_leading_space = true;
self.advance();
}
Some('\r') | Some('\n') => {
break;
}
Some('\\') => {
let after_bs = self.peek(1);
if after_bs == Some('\n') {
self.advance_by(2);
self.line += 1;
self.column = 1;
} else if after_bs == Some('\r') && self.peek(2) == Some('\n') {
self.advance_by(3);
self.line += 1;
self.column = 1;
} else {
break;
}
}
Some('/') if self.peek(1) == Some('/') => {
self.skip_line_comment();
}
Some('/') if self.peek(1) == Some('*') => {
self.skip_block_comment();
}
_ => break,
}
}
}
fn skip_line_comment(&mut self) {
self.advance_by(2); while let Some(c) = self.cur() {
if c == '\n' {
break;
}
self.advance();
}
}
fn skip_block_comment(&mut self) {
let start_line = self.line;
let start_col = self.column;
self.advance_by(2); let mut depth: u32 = 1;
while depth > 0 {
match self.cur() {
None => {
self.errors.push(format!(
"{}:{}: unterminated /* comment (started at {}:{})",
start_line, start_col, start_line, start_col
));
return;
}
Some('/') if self.peek(1) == Some('*') => {
self.advance_by(2);
depth += 1;
}
Some('*') if self.peek(1) == Some('/') => {
self.advance_by(2);
depth -= 1;
}
Some('\n') => {
self.advance();
self.line += 1;
self.column = 1;
}
Some('\r') => {
self.advance();
if self.cur() == Some('\n') {
self.advance();
}
self.line += 1;
self.column = 1;
}
Some(_) => {
self.advance();
}
}
}
}
fn lex_identifier_or_keyword(&mut self, loc: SourceLoc) -> Token {
let start = self.pos;
while let Some(c) = self.cur() {
if is_ident_continue(c) {
self.advance();
} else {
break;
}
}
let text = &self.source[start..self.pos];
let kind = if self.standard.is_gnu()
|| matches!(
self.standard,
CLangStandard::C99 | CLangStandard::C11 | CLangStandard::C17 | CLangStandard::C23
) {
KeywordTable::lookup_or_ident(text)
} else {
let kw = KeywordTable::lookup(text);
match kw {
Some(TokenKind::KwRestrict)
| Some(TokenKind::KwInline)
| Some(TokenKind::KwBool)
| Some(TokenKind::KwComplex)
| Some(TokenKind::KwImaginary)
| Some(TokenKind::KwAlignas)
| Some(TokenKind::KwAlignof)
| Some(TokenKind::KwAtomic)
| Some(TokenKind::KwGeneric)
| Some(TokenKind::KwNoreturn)
| Some(TokenKind::KwStaticAssert)
| Some(TokenKind::KwThreadLocal)
| Some(TokenKind::KwAsm)
| Some(TokenKind::KwTypeof)
| Some(TokenKind::KwAttribute)
| Some(TokenKind::KwExtension)
| Some(TokenKind::KwBuiltinVaArg)
| Some(TokenKind::KwBuiltinOffsetof)
| Some(TokenKind::KwBuiltinChooseExpr)
| Some(TokenKind::KwBuiltinTypesCompatible)
| Some(TokenKind::KwLabel)
| Some(TokenKind::KwInline2)
| Some(TokenKind::KwVolatile2)
| Some(TokenKind::KwConst2)
| Some(TokenKind::KwSigned2)
| Some(TokenKind::KwAlignof2) => TokenKind::Identifier,
other => other.unwrap_or(TokenKind::Identifier),
}
};
let mut flags = TokenFlags::none();
flags.is_at_start_of_line = self.at_start_of_line;
flags.has_leading_space = self.has_leading_space;
self.at_start_of_line = false;
Token::with_flags(kind, text, loc, flags)
}
fn lex_number(&mut self, loc: SourceLoc) -> Token {
let start = self.pos;
let (radix, is_float) = if self.cur() == Some('0') {
match self.peek(1) {
Some('x') | Some('X') => {
self.advance_by(2);
(16, false)
}
Some('b') | Some('B') => {
self.advance_by(2);
(2, false)
}
_ => {
if self.peek(1) == Some('.') {
(10, false)
} else {
(8, false)
}
}
}
} else {
(10, false)
};
let mut saw_dot = false;
let mut saw_exp = false;
let mut is_float = is_float;
loop {
match self.cur() {
Some(c) if c.is_ascii_digit() || (radix == 16 && is_hex_digit(c)) => {
self.advance();
}
Some('.') if (radix == 10 || radix == 16) && !saw_dot && !saw_exp => {
let next = self.peek(1);
if next.map_or(false, |c| c.is_ascii_digit() || is_hex_digit(c))
|| next == Some('e')
|| next == Some('E')
|| next == Some('p')
|| next == Some('P')
{
saw_dot = true;
is_float = true;
self.advance();
} else {
break;
}
}
Some('e') | Some('E') if !saw_exp && (radix == 10 || radix == 8) => {
saw_exp = true;
is_float = true;
self.advance();
if self.cur() == Some('+') || self.cur() == Some('-') {
self.advance();
}
}
Some('p') | Some('P') if !saw_exp && radix == 16 => {
saw_exp = true;
is_float = true;
self.advance();
if self.cur() == Some('+') || self.cur() == Some('-') {
self.advance();
}
}
_ => break,
}
}
if is_float {
self.consume_float_suffix();
} else {
self.consume_int_suffix();
}
let text = &self.source[start..self.pos];
let kind = if is_float {
TokenKind::NumericLiteral
} else {
TokenKind::NumericLiteral
};
let mut flags = TokenFlags::none();
flags.is_at_start_of_line = self.at_start_of_line;
flags.has_leading_space = self.has_leading_space;
self.at_start_of_line = false;
Token::with_flags(kind, text, loc, flags)
}
fn consume_int_suffix(&mut self) {
let mut saw_u = false;
let mut saw_l = 0u8;
loop {
match self.cur() {
Some('u') | Some('U') if !saw_u => {
saw_u = true;
self.advance();
}
Some('l') | Some('L') if saw_l < 2 => {
saw_l += 1;
self.advance();
}
Some('i') | Some('I') => {
self.advance();
}
_ => break,
}
}
}
fn consume_float_suffix(&mut self) {
match self.cur() {
Some('f') | Some('F') | Some('l') | Some('L') => {
self.advance();
}
_ => {}
}
}
fn lex_char_or_string_literal(&mut self, loc: SourceLoc, delimiter: char) -> Token {
let start = self.pos;
let prefix = self.parse_literal_prefix();
self.advance();
let kind = if delimiter == '\'' {
TokenKind::CharLiteral
} else {
TokenKind::StringLiteral
};
loop {
match self.cur() {
None => {
let label = if delimiter == '\'' {
"character"
} else {
"string"
};
self.errors.push(format!(
"{}:{}: unterminated {} literal",
loc.line, loc.column, label
));
break;
}
Some('\\') => {
self.advance();
if self.cur().is_some() {
self.consume_escape_sequence();
}
}
Some(c) if c == delimiter => {
self.advance(); break;
}
Some('\n') => {
self.errors.push(format!(
"{}:{}: newline in {} literal",
loc.line,
loc.column,
if delimiter == '\'' {
"character"
} else {
"string"
}
));
self.advance();
self.line += 1;
self.column = 1;
}
Some(_) => {
self.advance();
}
}
}
let text = &self.source[start..self.pos];
let mut flags = TokenFlags::none();
flags.is_at_start_of_line = self.at_start_of_line;
flags.has_leading_space = self.has_leading_space;
self.at_start_of_line = false;
Token::with_flags(kind, text, loc, flags)
}
fn parse_literal_prefix(&self) -> Option<CharPrefix> {
let rest = &self.source[self.pos..];
if rest.starts_with("u8'") || rest.starts_with("u8\"") {
Some(CharPrefix::U8)
} else if rest.starts_with("L'") || rest.starts_with("L\"") {
Some(CharPrefix::L)
} else if rest.starts_with("U'") || rest.starts_with("U\"") {
Some(CharPrefix::U)
} else if rest.starts_with("u'") || rest.starts_with("u\"") {
Some(CharPrefix::U16)
} else {
Some(CharPrefix::None)
}
}
fn consume_escape_sequence(&mut self) {
match self.cur() {
Some('\\') | Some('\'') | Some('"') | Some('?') => {
self.advance();
}
Some('a') | Some('b') | Some('f') | Some('n') | Some('r') | Some('t') | Some('v') => {
self.advance();
}
Some(c) if is_octal_digit(c) => {
self.advance();
if self.cur().map_or(false, is_octal_digit) {
self.advance();
if self.cur().map_or(false, is_octal_digit) {
self.advance();
}
}
}
Some('x') => {
self.advance();
while self.cur().map_or(false, is_hex_digit) {
self.advance();
}
}
Some('u') => {
self.advance();
for _ in 0..4 {
if self.cur().map_or(false, is_hex_digit) {
self.advance();
}
}
}
Some('U') => {
self.advance();
for _ in 0..8 {
if self.cur().map_or(false, is_hex_digit) {
self.advance();
}
}
}
Some(_) => {
self.advance();
}
None => {}
}
}
fn lex_punctuator(&mut self, loc: SourceLoc, ch: char) -> Option<Token> {
let ch1 = self.peek(1);
let ch2 = self.peek(2);
if let (Some(c1), Some(c2)) = (ch1, ch2) {
if let Some((kind, len)) = lookup_triple_punctuator(ch, c1, c2) {
self.advance_by(len);
let text = &self.source[loc.offset..self.pos];
let mut flags = TokenFlags::none();
flags.is_at_start_of_line = self.at_start_of_line;
flags.has_leading_space = self.has_leading_space;
self.at_start_of_line = false;
return Some(Token::with_flags(kind, text, loc, flags));
}
}
if let Some((kind, len)) = lookup_punctuator(ch, ch1) {
let actual_len = if kind == TokenKind::Ellipsis && len == 3 {
if ch2 != Some('.') {
1 } else {
3
}
} else {
len
};
self.advance_by(actual_len);
let text = &self.source[loc.offset..self.pos];
let actual_kind = if actual_len == 1 && ch == '.' {
TokenKind::Dot
} else {
kind
};
let mut flags = TokenFlags::none();
flags.is_at_start_of_line = self.at_start_of_line;
flags.has_leading_space = self.has_leading_space;
self.at_start_of_line = false;
return Some(Token::with_flags(actual_kind, text, loc, flags));
}
if ch == '.' {
self.advance();
let mut flags = TokenFlags::none();
flags.is_at_start_of_line = self.at_start_of_line;
flags.has_leading_space = self.has_leading_space;
self.at_start_of_line = false;
return Some(Token::with_flags(TokenKind::Dot, ".", loc, flags));
}
None
}
pub fn parse_int_literal(text: &str) -> (u64, bool) {
let s = text.trim();
if s.is_empty() {
return (0, false);
}
let (body, suffix) = Self::split_numeric_suffix(s);
let is_unsigned = IntSuffix::parse(suffix).is_unsigned();
let (radix, digits) = if body.starts_with("0x") || body.starts_with("0X") {
(16, &body[2..])
} else if body.starts_with("0b") || body.starts_with("0B") {
(2, &body[2..])
} else if body.starts_with('0') && body.len() > 1 {
(8, &body[1..])
} else {
(10, body)
};
let mut value: u64 = 0;
for c in digits.chars() {
let digit = match radix {
2 if c == '0' || c == '1' => (c as u64) - ('0' as u64),
8 if is_octal_digit(c) => (c as u64) - ('0' as u64),
10 if c.is_ascii_digit() => (c as u64) - ('0' as u64),
16 if is_hex_digit(c) => hex_value(c) as u64,
_ => continue, };
value = value.saturating_mul(radix as u64).saturating_add(digit);
}
(value, is_unsigned)
}
pub fn parse_float_literal(text: &str) -> f64 {
let s = text.trim();
if s.is_empty() {
return 0.0;
}
let (body, _suffix) = Self::split_numeric_suffix(s);
if body.starts_with("0x") || body.starts_with("0X") {
return Self::parse_hex_float(body);
}
body.parse::<f64>().unwrap_or(0.0)
}
fn parse_hex_float(s: &str) -> f64 {
let s = &s[2..];
let (before_dot, after_dot, exponent) = if let Some(dot_idx) = s.find('.') {
let p_idx = s.find(|c: char| c == 'p' || c == 'P');
match p_idx {
Some(p) => (&s[..dot_idx], &s[dot_idx + 1..p], &s[p + 1..]),
None => (&s[..dot_idx], &s[dot_idx + 1..], "0"),
}
} else {
let p_idx = s.find(|c: char| c == 'p' || c == 'P');
match p_idx {
Some(p) => (&s[..p], "", &s[p + 1..]),
None => (s, "", "0"),
}
};
let int_part = u64::from_str_radix(before_dot, 16).unwrap_or(0);
let frac_part = if after_dot.is_empty() {
0u64
} else {
u64::from_str_radix(after_dot, 16).unwrap_or(0)
};
let frac_bits = after_dot.len() as u32 * 4;
let exp_val: i32 = exponent.parse().unwrap_or(0);
let mantissa = (int_part as f64) + (frac_part as f64) / (1u64 << frac_bits) as f64;
mantissa * (2.0f64).powi(exp_val)
}
fn split_numeric_suffix(s: &str) -> (&str, &str) {
let is_hex = s.starts_with("0x") || s.starts_with("0X");
let mut split = s.len();
for (i, c) in s.char_indices().rev() {
let is_body = c.is_ascii_digit()
|| (is_hex && is_hex_digit(c))
|| c == '.'
|| c == 'x'
|| c == 'X'
|| c == 'p'
|| c == 'P'
|| c == 'e'
|| c == 'E'
|| c == '-'
|| c == '+';
if is_body {
split = i + c.len_utf8();
break;
}
}
(&s[..split], &s[split..])
}
pub fn decode_escape(chars: &mut impl Iterator<Item = char>) -> Option<char> {
match chars.next() {
None => None,
Some('\\') => Some('\\'),
Some('\'') => Some('\''),
Some('"') => Some('"'),
Some('?') => Some('?'),
Some('a') => Some('\x07'), Some('b') => Some('\x08'), Some('f') => Some('\x0C'), Some('n') => Some('\n'),
Some('r') => Some('\r'),
Some('t') => Some('\t'),
Some('v') => Some('\x0B'), Some('e') => Some('\x1B'), Some('0') => {
Some('\0')
}
Some(c @ '1'..='7') => {
let mut val = (c as u32) - ('0' as u32);
if let Some(c2 @ '0'..='7') = chars.next() {
val = val * 8 + (c2 as u32) - ('0' as u32);
if let Some(c3 @ '0'..='7') = chars.next() {
val = val * 8 + (c3 as u32) - ('0' as u32);
}
}
char::from_u32(val)
}
Some('x') => {
let mut val: u32 = 0;
let mut count = 0;
while let Some(c) = chars.next() {
if !is_hex_digit(c) || count >= 2 {
break;
}
val = val * 16 + hex_value(c);
count += 1;
}
char::from_u32(val)
}
Some('u') => {
let mut val: u32 = 0;
for _ in 0..4 {
match chars.next() {
Some(c) if is_hex_digit(c) => val = val * 16 + hex_value(c),
_ => break,
}
}
char::from_u32(val)
}
Some('U') => {
let mut val: u32 = 0;
for _ in 0..8 {
match chars.next() {
Some(c) if is_hex_digit(c) => val = val * 16 + hex_value(c),
_ => break,
}
}
char::from_u32(val)
}
Some(_) => None, }
}
}
pub fn tokenize(source: &str, standard: CLangStandard) -> Vec<Token> {
let mut lexer = Lexer::new(source, standard);
lexer.lex_all().to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
fn token_kinds(source: &str) -> Vec<TokenKind> {
let tokens = tokenize(source, CLangStandard::C11);
tokens.iter().map(|t| t.kind).collect()
}
fn token_texts(source: &str) -> Vec<String> {
let tokens = tokenize(source, CLangStandard::C11);
tokens.iter().map(|t| t.text.clone()).collect()
}
#[test]
fn test_keyword_int() {
let kinds = token_kinds("int");
assert_eq!(kinds, vec![TokenKind::KwInt, TokenKind::Eof]);
}
#[test]
fn test_keyword_return() {
let kinds = token_kinds("return");
assert_eq!(kinds, vec![TokenKind::KwReturn, TokenKind::Eof]);
}
#[test]
fn test_keyword_if_else_while_for() {
let kinds = token_kinds("if else while for");
assert_eq!(
kinds,
vec![
TokenKind::KwIf,
TokenKind::KwElse,
TokenKind::KwWhile,
TokenKind::KwFor,
TokenKind::Eof,
]
);
}
#[test]
fn test_all_c89_keywords() {
let source = "auto break case char const continue default do double else enum \
extern float for goto if inline int long register restrict return \
short signed sizeof static struct switch typedef union unsigned \
void volatile while";
let kinds = token_kinds(source);
for k in &kinds[..kinds.len() - 1] {
assert!(
k.is_keyword() || *k == TokenKind::Newline,
"expected keyword, got {:?}",
k
);
}
}
#[test]
fn test_c11_keywords() {
let source = "_Alignas _Alignof _Atomic _Generic _Noreturn _Static_assert _Thread_local";
let kinds = token_kinds(source);
assert_eq!(
kinds,
vec![
TokenKind::KwAlignas,
TokenKind::KwAlignof,
TokenKind::KwAtomic,
TokenKind::KwGeneric,
TokenKind::KwNoreturn,
TokenKind::KwStaticAssert,
TokenKind::KwThreadLocal,
TokenKind::Eof,
]
);
}
#[test]
fn test_gnu_keywords() {
let source = "__asm__ __typeof__ __attribute__ __extension__";
let kinds = token_kinds(source);
assert_eq!(
kinds,
vec![
TokenKind::KwAsm,
TokenKind::KwTypeof,
TokenKind::KwAttribute,
TokenKind::KwExtension,
TokenKind::Eof,
]
);
}
#[test]
fn test_identifier_simple() {
let kinds = token_kinds("foo bar baz_123 _private");
assert_eq!(
kinds,
vec![
TokenKind::Identifier,
TokenKind::Identifier,
TokenKind::Identifier,
TokenKind::Identifier,
TokenKind::Eof,
]
);
let texts = token_texts("foo bar baz_123 _private");
assert_eq!(texts, vec!["foo", "bar", "baz_123", "_private", ""]);
}
#[test]
fn test_decimal_integer() {
let kinds = token_kinds("0 42 123456 007");
assert_eq!(
kinds,
vec![
TokenKind::NumericLiteral,
TokenKind::NumericLiteral,
TokenKind::NumericLiteral,
TokenKind::NumericLiteral,
TokenKind::Eof,
]
);
}
#[test]
fn test_hex_integer() {
let kinds = token_kinds("0x0 0xDEAD 0xBEEF 0XABC");
assert_eq!(
kinds,
vec![
TokenKind::NumericLiteral,
TokenKind::NumericLiteral,
TokenKind::NumericLiteral,
TokenKind::NumericLiteral,
TokenKind::Eof,
]
);
}
#[test]
fn test_binary_integer() {
let kinds = token_kinds("0b0 0b1010 0B1111");
assert_eq!(
kinds,
vec![
TokenKind::NumericLiteral,
TokenKind::NumericLiteral,
TokenKind::NumericLiteral,
TokenKind::Eof,
]
);
}
#[test]
fn test_integer_suffixes() {
let kinds = token_kinds("1u 2U 3l 4L 5ul 6UL 7ll 8LL 9ull 10ULL 11lu 12LU");
for k in &kinds[..kinds.len() - 1] {
assert_eq!(*k, TokenKind::NumericLiteral);
}
}
#[test]
fn test_parse_int_literal_decimal() {
assert_eq!(Lexer::parse_int_literal("42"), (42, false));
assert_eq!(Lexer::parse_int_literal("0"), (0, false));
assert_eq!(Lexer::parse_int_literal("255u"), (255, true));
assert_eq!(Lexer::parse_int_literal("100UL"), (100, true));
}
#[test]
fn test_parse_int_literal_hex() {
assert_eq!(Lexer::parse_int_literal("0xFF"), (255, false));
assert_eq!(Lexer::parse_int_literal("0xDEAD"), (0xDEAD, false));
assert_eq!(Lexer::parse_int_literal("0x0"), (0, false));
}
#[test]
fn test_parse_int_literal_octal() {
assert_eq!(Lexer::parse_int_literal("077"), (63, false));
assert_eq!(Lexer::parse_int_literal("010"), (8, false));
}
#[test]
fn test_parse_int_literal_binary() {
assert_eq!(Lexer::parse_int_literal("0b1010"), (10, false));
assert_eq!(Lexer::parse_int_literal("0B1111"), (15, false));
}
#[test]
fn test_float_literals() {
let kinds = token_kinds("1.0 0.5 .5 1e10 2.5e-3 3.14f 2.0L 0x1.0p3");
for k in &kinds[..kinds.len() - 1] {
assert_eq!(*k, TokenKind::NumericLiteral);
}
}
#[test]
fn test_parse_float_literal_simple() {
let v = Lexer::parse_float_literal("3.14");
assert!((v - 3.14).abs() < 0.001);
}
#[test]
fn test_parse_float_literal_exponent() {
let v = Lexer::parse_float_literal("1e10");
assert!((v - 1e10).abs() < 1e5);
}
#[test]
fn test_parse_float_literal_hex() {
let v = Lexer::parse_float_literal("0x1.0p3");
assert_eq!(v, 8.0); }
#[test]
fn test_parse_float_literal_hex2() {
let v = Lexer::parse_float_literal("0x1.8p1");
assert_eq!(v, 3.0); }
#[test]
fn test_char_literal_simple() {
let kinds = token_kinds("'a' 'Z' '0'");
assert_eq!(
kinds,
vec![
TokenKind::CharLiteral,
TokenKind::CharLiteral,
TokenKind::CharLiteral,
TokenKind::Eof,
]
);
}
#[test]
fn test_char_literal_escape() {
let kinds = token_kinds(r"'\n' '\t' '\\' '\'' '\0'");
for k in &kinds[..kinds.len() - 1] {
assert_eq!(*k, TokenKind::CharLiteral);
}
}
#[test]
fn test_char_literal_hex_escape() {
let kinds = token_kinds(r"'\x41' '\x5A'");
assert_eq!(
kinds,
vec![
TokenKind::CharLiteral,
TokenKind::CharLiteral,
TokenKind::Eof,
]
);
}
#[test]
fn test_string_literal_simple() {
let kinds = token_kinds(r#""hello" "world""#);
assert_eq!(
kinds,
vec![
TokenKind::StringLiteral,
TokenKind::StringLiteral,
TokenKind::Eof,
]
);
}
#[test]
fn test_string_literal_escape() {
let kinds = token_kinds(r#""hello\nworld" "tab\there""#);
assert_eq!(
kinds,
vec![
TokenKind::StringLiteral,
TokenKind::StringLiteral,
TokenKind::Eof,
]
);
}
#[test]
fn test_string_literal_empty() {
let kinds = token_kinds(r#""""#);
assert_eq!(kinds, vec![TokenKind::StringLiteral, TokenKind::Eof]);
}
#[test]
fn test_operators_arithmetic() {
let kinds = token_kinds("+ - * / % ++ --");
assert_eq!(
kinds,
vec![
TokenKind::Plus,
TokenKind::Minus,
TokenKind::Star,
TokenKind::Slash,
TokenKind::Percent,
TokenKind::PlusPlus,
TokenKind::MinusMinus,
TokenKind::Eof,
]
);
}
#[test]
fn test_operators_comparison() {
let kinds = token_kinds("== != < > <= >=");
assert_eq!(
kinds,
vec![
TokenKind::EqualEqual,
TokenKind::NotEqual,
TokenKind::Less,
TokenKind::Greater,
TokenKind::LessEqual,
TokenKind::GreaterEqual,
TokenKind::Eof,
]
);
}
#[test]
fn test_operators_logical() {
let kinds = token_kinds("&& || !");
assert_eq!(
kinds,
vec![
TokenKind::AndAnd,
TokenKind::OrOr,
TokenKind::Exclaim,
TokenKind::Eof,
]
);
}
#[test]
fn test_operators_bitwise() {
let kinds = token_kinds("& | ^ ~ << >>");
assert_eq!(
kinds,
vec![
TokenKind::Ampersand,
TokenKind::Pipe,
TokenKind::Caret,
TokenKind::Tilde,
TokenKind::LessLess,
TokenKind::GreaterGreater,
TokenKind::Eof,
]
);
}
#[test]
fn test_compound_assignment() {
let kinds = token_kinds("+= -= *= /= %= &= |= ^= <<= >>=");
assert_eq!(
kinds,
vec![
TokenKind::PlusEqual,
TokenKind::MinusEqual,
TokenKind::StarEqual,
TokenKind::SlashEqual,
TokenKind::PercentEqual,
TokenKind::AmpersandEqual,
TokenKind::PipeEqual,
TokenKind::CaretEqual,
TokenKind::LessLessEqual,
TokenKind::GreaterGreaterEqual,
TokenKind::Eof,
]
);
}
#[test]
fn test_delimiters() {
let kinds = token_kinds("( ) [ ] { } ; , : ?");
assert_eq!(
kinds,
vec![
TokenKind::LParen,
TokenKind::RParen,
TokenKind::LBracket,
TokenKind::RBracket,
TokenKind::LBrace,
TokenKind::RBrace,
TokenKind::Semicolon,
TokenKind::Comma,
TokenKind::Colon,
TokenKind::Question,
TokenKind::Eof,
]
);
}
#[test]
fn test_arrow_and_dot() {
let kinds = token_kinds("-> .");
assert_eq!(
kinds,
vec![TokenKind::Arrow, TokenKind::Dot, TokenKind::Eof,]
);
}
#[test]
fn test_ellipsis() {
let kinds = token_kinds("...");
assert_eq!(kinds, vec![TokenKind::Ellipsis, TokenKind::Eof]);
}
#[test]
fn test_hash_and_hashhash() {
let kinds = token_kinds("# ##");
assert_eq!(
kinds,
vec![TokenKind::Hash, TokenKind::HashHash, TokenKind::Eof,]
);
}
#[test]
fn test_assignment() {
let kinds = token_kinds("= == != += -=");
assert_eq!(
kinds,
vec![
TokenKind::Equal,
TokenKind::EqualEqual,
TokenKind::NotEqual,
TokenKind::PlusEqual,
TokenKind::MinusEqual,
TokenKind::Eof,
]
);
}
#[test]
fn test_line_comment() {
let kinds = token_kinds("int x; // this is a comment\nint y;");
assert!(kinds.contains(&TokenKind::KwInt));
assert!(!kinds.contains(&TokenKind::Comment));
let texts = token_texts("int x; // this is a comment\nint y;");
assert_eq!(texts[0], "int");
let y_pos = texts.iter().position(|t| t == "y").expect("y not found");
assert_eq!(texts[y_pos], "y");
}
#[test]
fn test_block_comment() {
let kinds = token_kinds("int /* comment */ x;");
assert_eq!(
&kinds[..kinds.len() - 1],
&[
TokenKind::KwInt,
TokenKind::Identifier,
TokenKind::Semicolon
]
);
}
#[test]
fn test_block_comment_multiline() {
let kinds = token_kinds("/* line1\n line2 */ int x;");
assert_eq!(
&kinds[..kinds.len() - 1],
&[
TokenKind::KwInt,
TokenKind::Identifier,
TokenKind::Semicolon
]
);
}
#[test]
fn test_nested_block_comment() {
let kinds = token_kinds("/* outer /* inner */ still outer */ int x;");
assert_eq!(
&kinds[..kinds.len() - 1],
&[
TokenKind::KwInt,
TokenKind::Identifier,
TokenKind::Semicolon
]
);
}
#[test]
fn test_whitespace_skipping() {
let kinds = token_kinds("int \t x;");
assert_eq!(
&kinds[..kinds.len() - 1],
&[
TokenKind::KwInt,
TokenKind::Identifier,
TokenKind::Semicolon
]
);
}
#[test]
fn test_newline_separation() {
let kinds = token_kinds("int\nx\n;");
assert_eq!(
&kinds[..kinds.len() - 1],
&[
TokenKind::KwInt,
TokenKind::Newline,
TokenKind::Identifier,
TokenKind::Newline,
TokenKind::Semicolon
]
);
}
#[test]
fn test_simple_function() {
let source = "int main(void) { return 0; }";
let kinds = token_kinds(source);
assert_eq!(
&kinds[..kinds.len() - 1],
&[
TokenKind::KwInt,
TokenKind::Identifier, TokenKind::LParen,
TokenKind::KwVoid,
TokenKind::RParen,
TokenKind::LBrace,
TokenKind::KwReturn,
TokenKind::NumericLiteral, TokenKind::Semicolon,
TokenKind::RBrace,
]
);
}
#[test]
fn test_pointer_declaration() {
let source = "int *p = NULL;";
let kinds = token_kinds(source);
assert_eq!(
&kinds[..kinds.len() - 1],
&[
TokenKind::KwInt,
TokenKind::Star,
TokenKind::Identifier, TokenKind::Equal,
TokenKind::Identifier, TokenKind::Semicolon,
]
);
}
#[test]
fn test_struct_declaration() {
let source = "struct Point { int x; int y; };";
let kinds = token_kinds(source);
assert_eq!(kinds[0], TokenKind::KwStruct);
assert_eq!(kinds[1], TokenKind::Identifier); assert_eq!(kinds[2], TokenKind::LBrace);
assert!(kinds.contains(&TokenKind::Semicolon));
}
#[test]
fn test_decode_escape_simple() {
let s = "n";
let mut chars = s.chars();
assert_eq!(Lexer::decode_escape(&mut chars), Some('\n'));
let s = "t";
let mut chars = s.chars();
assert_eq!(Lexer::decode_escape(&mut chars), Some('\t'));
let s = "\\\\";
let mut chars = s.chars();
assert_eq!(Lexer::decode_escape(&mut chars), Some('\\'));
}
#[test]
fn test_decode_escape_octal() {
let s = "101"; let mut chars = s.chars();
assert_eq!(Lexer::decode_escape(&mut chars), Some('A'));
}
#[test]
fn test_decode_escape_hex() {
let s = "x41"; let mut chars = s.chars();
assert_eq!(Lexer::decode_escape(&mut chars), Some('A'));
}
#[test]
fn test_decode_escape_null() {
let s = "0";
let mut chars = s.chars();
assert_eq!(Lexer::decode_escape(&mut chars), Some('\0'));
}
#[test]
fn test_unterminated_string_error() {
let mut lexer = Lexer::new("\"hello", CLangStandard::C11);
lexer.lex_all();
assert!(lexer.error_count() > 0);
assert!(lexer.errors()[0].contains("unterminated"));
}
#[test]
fn test_unterminated_char_error() {
let mut lexer = Lexer::new("'x", CLangStandard::C11);
lexer.lex_all();
assert!(lexer.error_count() > 0);
}
#[test]
fn test_error_collection() {
let mut lexer = Lexer::new("int @ foo", CLangStandard::C11);
lexer.lex_all();
assert_eq!(lexer.error_count(), 1);
}
#[test]
fn test_split_numeric_suffix() {
assert_eq!(Lexer::split_numeric_suffix("42UL"), ("42", "UL"));
assert_eq!(Lexer::split_numeric_suffix("0xFFu"), ("0xFF", "u"));
assert_eq!(Lexer::split_numeric_suffix("1.0f"), ("1.0", "f"));
assert_eq!(Lexer::split_numeric_suffix("100"), ("100", ""));
}
#[test]
fn test_c89_restrict_is_identifier() {
let tokens = tokenize("restrict", CLangStandard::C89);
assert_eq!(tokens[0].kind, TokenKind::Identifier);
assert_eq!(tokens[0].text, "restrict");
}
#[test]
fn test_c89_inline_is_identifier() {
let tokens = tokenize("inline", CLangStandard::C89);
assert_eq!(tokens[0].kind, TokenKind::Identifier);
}
#[test]
fn test_peek_token() {
let lexer = Lexer::new("int x;", CLangStandard::C11);
let peeked = lexer.peek_token();
assert!(peeked.is_some());
assert_eq!(peeked.unwrap().kind, TokenKind::KwInt);
}
#[test]
fn test_peek_token_empty() {
let lexer = Lexer::new("", CLangStandard::C11);
let peeked = lexer.peek_token();
assert!(peeked.is_none());
}
}