use crate::span::Span;
use crate::token::{KEYWORDS, Radix, Token, TokenKind, TokenValue, Trivia, TriviaKind};
use alloc::string::String;
use alloc::vec::Vec;
#[must_use]
pub fn lex(source: &str) -> Vec<Token> {
Lexer::new(source).collect_all()
}
#[must_use]
pub fn literal_value(kind: TokenKind, text: &str) -> TokenValue {
let mut lexer = Lexer::new(text);
let token = lexer.next_token();
if token.kind != kind || token.span.end as usize != text.len() || token.span.start != 0 {
return TokenValue::None;
}
token.value
}
pub struct Lexer<'a> {
source: &'a str,
bytes: &'a [u8],
offset: usize,
}
impl<'a> Lexer<'a> {
#[must_use]
pub const fn new(source: &'a str) -> Self {
Self {
source,
bytes: source.as_bytes(),
offset: 0,
}
}
#[must_use]
pub fn collect_all(mut self) -> Vec<Token> {
let mut out = Vec::new();
loop {
let token = self.next_token();
let is_eof = token.kind == TokenKind::Eof;
out.push(token);
if is_eof {
return out;
}
}
}
pub fn next_token(&mut self) -> Token {
let leading = self.scan_trivia();
let start = self.offset;
let Some(byte) = self.peek() else {
return Token {
kind: TokenKind::Eof,
span: Span::new(self.pos(), self.pos()),
leading,
value: TokenValue::None,
};
};
if byte == b'L' {
match self.peek_at(1) {
Some(b'\'') => {
self.bump();
return self.lex_char(start, leading, true);
}
Some(b'"') => {
self.bump();
return self.lex_string(start, leading, true);
}
_ => {}
}
}
if byte == b'_' || byte.is_ascii_alphabetic() {
return self.lex_word(start, leading);
}
if byte.is_ascii_digit()
|| (byte == b'.' && self.peek_at(1).is_some_and(|b| b.is_ascii_digit()))
{
return self.lex_number(start, leading);
}
match byte {
b'\'' => self.lex_char(start, leading, false),
b'"' => self.lex_string(start, leading, false),
_ => self.lex_punct(start, leading),
}
}
fn scan_trivia(&mut self) -> Vec<Trivia> {
let mut out = Vec::new();
loop {
let start = self.offset;
let Some(byte) = self.peek() else {
return out;
};
let kind = match byte {
b'\n' => {
self.bump();
TriviaKind::Newline
}
b' ' | b'\t' | b'\r' => {
while self
.peek()
.is_some_and(|b| matches!(b, b' ' | b'\t' | b'\r'))
{
self.bump();
}
TriviaKind::Whitespace
}
b'/' if self.peek_at(1) == Some(b'/') => {
while self.peek().is_some_and(|b| b != b'\n') {
self.bump();
}
TriviaKind::LineComment
}
b'/' if self.peek_at(1) == Some(b'*') => {
self.bump();
self.bump();
while self.peek().is_some() {
if self.peek() == Some(b'*') && self.peek_at(1) == Some(b'/') {
self.bump();
self.bump();
break;
}
self.bump();
}
TriviaKind::BlockComment
}
b'#' if self.at_line_start(start) => {
while let Some(b) = self.peek() {
if b == b'\\' && self.peek_at(1) == Some(b'\n') {
self.bump();
self.bump();
continue;
}
if b == b'\n' {
break;
}
self.bump();
}
TriviaKind::Directive
}
_ => return out,
};
out.push(Trivia {
kind,
span: Span::new(start as u32, self.pos()),
});
}
}
fn at_line_start(&self, at: usize) -> bool {
let mut index = at;
while index > 0 {
index -= 1;
match self.bytes.get(index) {
Some(b'\n') => return true,
Some(b' ' | b'\t' | b'\r') => {}
_ => return false,
}
}
true
}
fn lex_word(&mut self, start: usize, leading: Vec<Trivia>) -> Token {
let escaped = self.peek() == Some(b'_');
if escaped {
self.bump();
}
while self
.peek()
.is_some_and(|b| b == b'_' || b.is_ascii_alphanumeric())
{
self.bump();
}
let span = Span::new(start as u32, self.pos());
let text = span.slice(self.source).unwrap_or("");
let word = if escaped { &text[1..] } else { text };
if escaped {
return Token {
kind: TokenKind::Ident,
span,
leading,
value: TokenValue::Ident { escaped: true },
};
}
let kind = KEYWORDS
.iter()
.find(|(name, _)| *name == word)
.map_or(TokenKind::Ident, |(_, kind)| *kind);
Token {
kind,
span,
leading,
value: if kind == TokenKind::Ident {
TokenValue::Ident { escaped: false }
} else {
TokenValue::None
},
}
}
#[allow(clippy::too_many_lines)]
fn lex_number(&mut self, start: usize, leading: Vec<Trivia>) -> Token {
let mut radix = Radix::Decimal;
let mut is_float = false;
if self.peek() == Some(b'0') {
match self.peek_at(1) {
Some(b'x' | b'X') => {
radix = Radix::Hex;
self.bump();
self.bump();
while self.peek().is_some_and(|b| b.is_ascii_hexdigit()) {
self.bump();
}
}
Some(b'0'..=b'7') if !self.decimal_ahead() => {
radix = Radix::Octal;
self.bump();
while self.peek().is_some_and(|b| (b'0'..=b'7').contains(&b)) {
self.bump();
}
}
_ => {
self.bump();
}
}
}
if radix == Radix::Decimal {
while self.peek().is_some_and(|b| b.is_ascii_digit()) {
self.bump();
}
if self.peek() == Some(b'.') {
is_float = true;
self.bump();
while self.peek().is_some_and(|b| b.is_ascii_digit()) {
self.bump();
}
}
if self.peek().is_some_and(|b| matches!(b, b'e' | b'E')) {
is_float = true;
self.bump();
if self.peek().is_some_and(|b| matches!(b, b'+' | b'-')) {
self.bump();
}
while self.peek().is_some_and(|b| b.is_ascii_digit()) {
self.bump();
}
}
}
if self.peek().is_some_and(|b| matches!(b, b'd' | b'D')) {
self.bump();
return Token {
kind: TokenKind::FixedLiteral,
span: Span::new(start as u32, self.pos()),
leading,
value: TokenValue::None,
};
}
let span = Span::new(start as u32, self.pos());
if is_float {
return Token {
kind: TokenKind::FloatLiteral,
span,
leading,
value: TokenValue::None,
};
}
let text = span.slice(self.source).unwrap_or("0");
let value = match radix {
Radix::Hex => i128::from_str_radix(text.get(2..).unwrap_or("0"), 16),
Radix::Octal => i128::from_str_radix(text.get(1..).unwrap_or("0"), 8),
Radix::Decimal => text.parse::<i128>(),
};
match value {
Ok(value) => Token {
kind: TokenKind::IntLiteral,
span,
leading,
value: TokenValue::Int { value, radix },
},
Err(_) => Token {
kind: TokenKind::Error,
span,
leading,
value: TokenValue::None,
},
}
}
fn decimal_ahead(&self) -> bool {
let mut at = 1;
while self.peek_at(at).is_some_and(|b| b.is_ascii_digit()) {
at += 1;
}
matches!(self.peek_at(at), Some(b'.' | b'e' | b'E' | b'd' | b'D'))
}
fn lex_char(&mut self, start: usize, leading: Vec<Trivia>, wide: bool) -> Token {
self.bump(); let mut value = 0u32;
let mut characters = 0usize;
let mut terminated = false;
while let Some(byte) = self.peek() {
if byte == b'\'' {
self.bump();
terminated = true;
break;
}
if byte == b'\n' {
break;
}
if byte == b'\\' {
self.bump();
value = self.scan_escape();
} else {
value = self.scan_utf8_scalar(byte);
}
characters += 1;
}
let representable = if wide { value <= 0xFFFF } else { value <= 0xFF };
let span = Span::new(start as u32, self.pos());
Token {
kind: if !terminated || characters != 1 || !representable {
TokenKind::Error
} else if wide {
TokenKind::WCharLiteral
} else {
TokenKind::CharLiteral
},
span,
leading,
value: TokenValue::Char { value },
}
}
fn scan_utf8_scalar(&mut self, first: u8) -> u32 {
self.bump();
let (mut value, extra) = match first {
0x00..=0x7F => return u32::from(first),
0xC2..=0xDF => (u32::from(first & 0x1F), 1),
0xE0..=0xEF => (u32::from(first & 0x0F), 2),
0xF0..=0xF4 => (u32::from(first & 0x07), 3),
_ => return u32::from(first),
};
for _ in 0..extra {
match self.peek() {
Some(byte @ 0x80..=0xBF) => {
value = (value << 6) | u32::from(byte & 0x3F);
self.bump();
}
_ => return value,
}
}
value
}
fn lex_string(&mut self, start: usize, leading: Vec<Trivia>, wide: bool) -> Token {
self.bump(); let mut text = String::new();
let mut terminated = false;
while let Some(byte) = self.peek() {
if byte == b'"' {
self.bump();
terminated = true;
break;
}
if byte == b'\n' {
break;
}
if byte == b'\\' {
self.bump();
if let Some(ch) = char::from_u32(self.scan_escape()) {
text.push(ch);
}
} else {
let from = self.offset;
self.bump_char();
if let Some(slice) = self.source.get(from..self.offset) {
text.push_str(slice);
}
}
}
let span = Span::new(start as u32, self.pos());
Token {
kind: if !terminated {
TokenKind::Error
} else if wide {
TokenKind::WStringLiteral
} else {
TokenKind::StringLiteral
},
span,
leading,
value: TokenValue::Text { value: text },
}
}
fn scan_escape(&mut self) -> u32 {
let Some(byte) = self.peek() else {
return 0;
};
self.bump();
match byte {
b'n' => u32::from(b'\n'),
b't' => u32::from(b'\t'),
b'v' => 0x0b,
b'b' => 0x08,
b'r' => u32::from(b'\r'),
b'f' => 0x0c,
b'a' => 0x07,
b'\\' => u32::from(b'\\'),
b'?' => u32::from(b'?'),
b'\'' => u32::from(b'\''),
b'"' => u32::from(b'"'),
b'0'..=b'7' => {
let mut value = u32::from(byte - b'0');
let mut digits = 1;
while digits < 3 && self.peek().is_some_and(|b| (b'0'..=b'7').contains(&b)) {
if let Some(next) = self.peek() {
value = value * 8 + u32::from(next - b'0');
self.bump();
digits += 1;
}
}
value
}
b'x' | b'X' => {
let mut value = 0u32;
let mut digits = 0;
while digits < 2 && self.peek().is_some_and(|b| b.is_ascii_hexdigit()) {
if let Some(next) = self.peek() {
value = value * 16 + u32::from(hex_value(next));
self.bump();
digits += 1;
}
}
value
}
b'u' | b'U' => {
let mut value = 0u32;
let mut digits = 0;
while digits < 4 && self.peek().is_some_and(|b| b.is_ascii_hexdigit()) {
if let Some(next) = self.peek() {
value = value * 16 + u32::from(hex_value(next));
self.bump();
digits += 1;
}
}
value
}
other => u32::from(other),
}
}
fn lex_punct(&mut self, start: usize, leading: Vec<Trivia>) -> Token {
let Some(byte) = self.peek() else {
return Token {
kind: TokenKind::Eof,
span: Span::new(start as u32, self.pos()),
leading,
value: TokenValue::None,
};
};
self.bump();
let kind = match byte {
b';' => TokenKind::Semicolon,
b'{' => TokenKind::LBrace,
b'}' => TokenKind::RBrace,
b'(' => TokenKind::LParen,
b')' => TokenKind::RParen,
b'[' => TokenKind::LBracket,
b']' => TokenKind::RBracket,
b',' => TokenKind::Comma,
b'=' => TokenKind::Equals,
b'+' => TokenKind::Plus,
b'-' => TokenKind::Minus,
b'*' => TokenKind::Star,
b'/' => TokenKind::Slash,
b'%' => TokenKind::Percent,
b'~' => TokenKind::Tilde,
b'|' => TokenKind::Pipe,
b'^' => TokenKind::Caret,
b'&' => TokenKind::Ampersand,
b'@' => TokenKind::At,
b'#' => TokenKind::Hash,
b':' => {
if self.peek() == Some(b':') {
self.bump();
TokenKind::ColonColon
} else {
TokenKind::Colon
}
}
b'<' => {
if self.peek() == Some(b'<') {
self.bump();
TokenKind::LessLess
} else {
TokenKind::Less
}
}
b'>' => {
if self.peek() == Some(b'>') {
self.bump();
TokenKind::GreaterGreater
} else {
TokenKind::Greater
}
}
_ => {
while self.peek().is_some_and(|b| !is_recognised_start(b)) {
self.bump();
}
TokenKind::Error
}
};
Token {
kind,
span: Span::new(start as u32, self.pos()),
leading,
value: TokenValue::None,
}
}
const fn peek(&self) -> Option<u8> {
if self.offset < self.bytes.len() {
Some(self.bytes[self.offset])
} else {
None
}
}
const fn peek_at(&self, ahead: usize) -> Option<u8> {
let at = self.offset + ahead;
if at < self.bytes.len() {
Some(self.bytes[at])
} else {
None
}
}
const fn bump(&mut self) {
if self.offset < self.bytes.len() {
self.offset += 1;
}
}
fn bump_char(&mut self) {
let rest = self.source.get(self.offset..).unwrap_or("");
let width = rest.chars().next().map_or(1, char::len_utf8);
self.offset = (self.offset + width).min(self.bytes.len());
}
#[allow(clippy::cast_possible_truncation)]
const fn pos(&self) -> u32 {
self.offset as u32
}
}
const fn hex_value(byte: u8) -> u8 {
match byte {
b'0'..=b'9' => byte - b'0',
b'a'..=b'f' => byte - b'a' + 10,
b'A'..=b'F' => byte - b'A' + 10,
_ => 0,
}
}
const fn is_recognised_start(byte: u8) -> bool {
byte.is_ascii_alphanumeric()
|| matches!(
byte,
b'_' | b'\''
| b'"'
| b';'
| b'{'
| b'}'
| b'('
| b')'
| b'['
| b']'
| b'<'
| b'>'
| b','
| b':'
| b'='
| b'+'
| b'-'
| b'*'
| b'/'
| b'%'
| b'~'
| b'|'
| b'^'
| b'&'
| b'@'
| b'#'
| b'.'
| b' '
| b'\t'
| b'\r'
| b'\n'
)
}