use rustc_hash::FxHashMap;
use oxc_allocator::Allocator;
use oxc_ast::ast::RegExpFlags;
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::{SourceType, Span};
use crate::{UniquePromise, diagnostics};
mod byte_handlers;
mod comment;
mod identifier;
mod jsx;
mod kind;
mod number;
mod numeric;
mod punctuation;
mod regex;
mod search;
mod source;
mod string;
mod template;
mod token;
mod trivia_builder;
mod typescript;
mod unicode;
mod whitespace;
pub use kind::Kind;
pub use number::{parse_big_int, parse_float, parse_int};
pub use token::Token;
use source::{Source, SourcePosition};
use trivia_builder::TriviaBuilder;
#[derive(Debug, Clone)]
pub struct LexerCheckpoint<'a> {
source_position: SourcePosition<'a>,
token: Token,
errors_snapshot: ErrorSnapshot,
}
#[derive(Debug, Clone)]
enum ErrorSnapshot {
Empty,
Count(usize),
Full(Vec<OxcDiagnostic>),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum LexerContext {
Regular,
JsxAttributeValue,
}
pub struct Lexer<'a> {
allocator: &'a Allocator,
source: Source<'a>,
source_type: SourceType,
token: Token,
pub(crate) errors: Vec<OxcDiagnostic>,
context: LexerContext,
pub(crate) trivia_builder: TriviaBuilder,
pub escaped_strings: FxHashMap<u32, &'a str>,
pub escaped_templates: FxHashMap<u32, Option<&'a str>>,
multi_line_comment_end_finder: Option<memchr::memmem::Finder<'static>>,
}
impl<'a> Lexer<'a> {
pub(super) fn new(
allocator: &'a Allocator,
source_text: &'a str,
source_type: SourceType,
unique: UniquePromise,
) -> Self {
let source = Source::new(source_text, unique);
let token = Token::new_on_new_line();
Self {
allocator,
source,
source_type,
token,
errors: vec![],
context: LexerContext::Regular,
trivia_builder: TriviaBuilder::default(),
escaped_strings: FxHashMap::default(),
escaped_templates: FxHashMap::default(),
multi_line_comment_end_finder: None,
}
}
#[cfg(feature = "benchmarking")]
pub fn new_for_benchmarks(
allocator: &'a Allocator,
source_text: &'a str,
source_type: SourceType,
) -> Self {
let unique = UniquePromise::new_for_tests_and_benchmarks();
Self::new(allocator, source_text, source_type, unique)
}
#[cfg(feature = "benchmarking")]
pub fn errors(&self) -> &[OxcDiagnostic] {
&self.errors
}
pub fn remaining(&self) -> &'a str {
self.source.remaining()
}
pub fn checkpoint(&self) -> LexerCheckpoint<'a> {
let errors_snapshot = if self.errors.is_empty() {
ErrorSnapshot::Empty
} else {
ErrorSnapshot::Count(self.errors.len())
};
LexerCheckpoint {
source_position: self.source.position(),
token: self.token,
errors_snapshot,
}
}
pub(crate) fn checkpoint_with_error_recovery(&self) -> LexerCheckpoint<'a> {
let errors_snapshot = if self.errors.is_empty() {
ErrorSnapshot::Empty
} else {
ErrorSnapshot::Full(self.errors.clone())
};
LexerCheckpoint {
source_position: self.source.position(),
token: self.token,
errors_snapshot,
}
}
pub fn rewind(&mut self, checkpoint: LexerCheckpoint<'a>) {
match checkpoint.errors_snapshot {
ErrorSnapshot::Empty => self.errors.clear(),
ErrorSnapshot::Count(len) => self.errors.truncate(len),
ErrorSnapshot::Full(errors) => self.errors = errors,
}
self.source.set_position(checkpoint.source_position);
self.token = checkpoint.token;
}
pub fn peek_token(&mut self) -> Token {
let checkpoint = self.checkpoint();
let token = self.next_token();
self.rewind(checkpoint);
token
}
pub fn set_context(&mut self, context: LexerContext) {
self.context = context;
}
pub fn first_token(&mut self) -> Token {
let kind = if let Some([b'#', b'!']) = self.peek_2_bytes() {
unsafe { self.read_hashbang_comment() }
} else {
self.read_next_token()
};
self.finish_next(kind)
}
pub fn next_token(&mut self) -> Token {
let kind = self.read_next_token();
self.finish_next(kind)
}
fn finish_next(&mut self, kind: Kind) -> Token {
self.token.set_kind(kind);
self.token.set_end(self.offset());
let token = self.token;
self.trivia_builder.handle_token(token);
self.token = Token::default();
token
}
#[inline]
pub fn advance_to_end(&mut self) {
self.source.advance_to_end();
}
fn error(&mut self, error: OxcDiagnostic) {
self.errors.push(error);
}
#[inline]
fn offset(&self) -> u32 {
self.source.offset()
}
fn unterminated_range(&self) -> Span {
Span::new(self.token.start(), self.offset())
}
#[inline]
fn next_char(&mut self) -> Option<char> {
self.source.next_char()
}
#[inline]
fn consume_char(&mut self) -> char {
self.source.next_char().unwrap()
}
#[inline]
fn next_2_chars(&mut self) -> Option<[char; 2]> {
self.source.next_2_chars()
}
#[inline]
fn consume_2_chars(&mut self) -> [char; 2] {
self.next_2_chars().unwrap()
}
#[inline]
fn peek_byte(&self) -> Option<u8> {
self.source.peek_byte()
}
#[inline]
fn peek_2_bytes(&self) -> Option<[u8; 2]> {
self.source.peek_2_bytes()
}
#[inline]
fn peek_char(&self) -> Option<char> {
self.source.peek_char()
}
#[expect(clippy::inline_always)]
#[inline(always)]
fn next_ascii_byte_eq(&mut self, b: u8) -> bool {
assert!(b.is_ascii());
unsafe { self.source.advance_if_ascii_eq(b) }
}
fn current_offset(&self) -> Span {
Span::empty(self.offset())
}
fn unexpected_err(&mut self) {
let offset = self.current_offset();
match self.peek_char() {
Some(c) => self.error(diagnostics::invalid_character(c, offset)),
None => self.error(diagnostics::unexpected_end(offset)),
}
}
#[inline] fn read_next_token(&mut self) -> Kind {
self.trivia_builder.has_pure_comment = false;
self.trivia_builder.has_no_side_effects_comment = false;
loop {
let offset = self.offset();
self.token.set_start(offset);
let Some(byte) = self.peek_byte() else {
return Kind::Eof;
};
let kind = unsafe { self.handle_byte(byte) };
if kind != Kind::Skip {
return kind;
}
}
}
}
#[cold]
pub fn cold_branch<F: FnOnce() -> T, T>(f: F) -> T {
f()
}