use crate::lexer::{JsLexContext, JsLexer, JsReLexContext, TextRange};
use crate::prelude::*;
use biome_js_syntax::JsSyntaxKind;
use biome_js_syntax::JsSyntaxKind::EOF;
use biome_parser::lexer::{BufferedLexer, LexContext};
use biome_parser::token_source::{TokenSourceCheckpoint, Trivia};
use biome_rowan::{TextSize, TriviaPieceKind};
use std::collections::VecDeque;
pub struct JsTokenSource<'l> {
lexer: BufferedLexer<'l, JsLexer<'l>>,
pub(super) trivia_list: Vec<Trivia>,
non_trivia_lookahead: VecDeque<Lookahead>,
lookahead_offset: usize,
}
#[derive(Debug, Copy, Clone)]
struct Lookahead {
kind: JsSyntaxKind,
after_newline: bool,
}
pub(crate) type JsTokenSourceCheckpoint = TokenSourceCheckpoint<JsSyntaxKind>;
impl<'l> JsTokenSource<'l> {
pub(crate) fn new(lexer: BufferedLexer<'l, JsLexer<'l>>) -> JsTokenSource<'l> {
JsTokenSource {
lexer,
trivia_list: vec![],
lookahead_offset: 0,
non_trivia_lookahead: VecDeque::new(),
}
}
pub fn from_str(source: &'l str) -> JsTokenSource<'l> {
let lexer = JsLexer::from_str(source);
let buffered = BufferedLexer::new(lexer);
let mut source = JsTokenSource::new(buffered);
source.next_non_trivia_token(JsLexContext::default(), true);
source
}
#[inline]
fn next_non_trivia_token(&mut self, context: JsLexContext, first_token: bool) {
let mut processed_tokens = 0;
let mut trailing = !first_token;
self.non_trivia_lookahead.pop_front();
loop {
let kind = self.lexer.next_token(context);
processed_tokens += 1;
let trivia_kind = TriviaPieceKind::try_from(kind);
match trivia_kind {
Err(_) => break,
Ok(trivia_kind) => {
if trivia_kind.is_newline() {
trailing = false;
}
self.trivia_list
.push(Trivia::new(trivia_kind, self.current_range(), trailing));
}
}
}
if self.lookahead_offset != 0 {
debug_assert!(self.lookahead_offset >= processed_tokens);
self.lookahead_offset -= processed_tokens;
}
}
#[inline(always)]
pub fn has_unicode_escape(&self) -> bool {
self.lexer.has_unicode_escape()
}
pub fn has_next_preceding_trivia(&mut self) -> bool {
let next_token_trivia = self
.lexer
.lookahead()
.next()
.and_then(|lookahead| TriviaPieceKind::try_from(lookahead.kind()).ok());
next_token_trivia.is_some()
}
#[inline(always)]
fn lookahead(&mut self, n: usize) -> Option<Lookahead> {
assert_ne!(n, 0);
if let Some(lookahead) = self.non_trivia_lookahead.get(n - 1) {
return Some(*lookahead);
}
let iter = self.lexer.lookahead().skip(self.lookahead_offset);
let mut remaining = n - self.non_trivia_lookahead.len();
for item in iter {
self.lookahead_offset += 1;
if !item.kind().is_trivia() {
remaining -= 1;
let lookahead = Lookahead {
after_newline: item.has_preceding_line_break(),
kind: item.kind(),
};
self.non_trivia_lookahead.push_back(lookahead);
if remaining == 0 {
return Some(lookahead);
}
}
}
None
}
pub fn re_lex(&mut self, mode: JsReLexContext) -> JsSyntaxKind {
let current_kind = self.current();
let new_kind = self.lexer.re_lex(mode);
if current_kind != new_kind {
self.non_trivia_lookahead.clear();
self.lookahead_offset = 0;
}
new_kind
}
pub fn checkpoint(&self) -> JsTokenSourceCheckpoint {
JsTokenSourceCheckpoint {
trivia_len: self.trivia_list.len() as u32,
lexer_checkpoint: self.lexer.checkpoint(),
}
}
pub fn rewind(&mut self, checkpoint: JsTokenSourceCheckpoint) {
assert!(self.trivia_list.len() >= checkpoint.trivia_len as usize);
self.trivia_list.truncate(checkpoint.trivia_len as usize);
self.lexer.rewind(checkpoint.lexer_checkpoint);
self.non_trivia_lookahead.clear();
self.lookahead_offset = 0;
}
}
impl<'source> TokenSource for JsTokenSource<'source> {
type Kind = JsSyntaxKind;
#[inline(always)]
fn current(&self) -> JsSyntaxKind {
self.lexer.current()
}
#[inline(always)]
fn current_range(&self) -> TextRange {
self.lexer.current_range()
}
#[inline(always)]
fn text(&self) -> &'source str {
self.lexer.source()
}
#[inline(always)]
fn position(&self) -> TextSize {
self.current_range().start()
}
#[inline(always)]
fn has_preceding_line_break(&self) -> bool {
self.lexer.has_preceding_line_break()
}
#[inline(always)]
fn bump(&mut self) {
self.bump_with_context(JsLexContext::Regular)
}
fn skip_as_trivia(&mut self) {
self.skip_as_trivia_with_context(JsLexContext::Regular)
}
fn finish(self) -> (Vec<Trivia>, Vec<ParseDiagnostic>) {
(self.trivia_list, self.lexer.finish())
}
}
impl<'source> BumpWithContext for JsTokenSource<'source> {
type Context = JsLexContext;
#[inline(always)]
fn bump_with_context(&mut self, context: Self::Context) {
if self.current() != EOF {
if !context.is_regular() {
self.lookahead_offset = 0;
self.non_trivia_lookahead.clear();
}
self.next_non_trivia_token(context, false);
}
}
fn skip_as_trivia_with_context(&mut self, context: JsLexContext) {
if self.current() != EOF {
if !context.is_regular() {
self.lookahead_offset = 0;
self.non_trivia_lookahead.clear();
}
self.trivia_list.push(Trivia::new(
TriviaPieceKind::Skipped,
self.current_range(),
false,
));
self.next_non_trivia_token(context, true)
}
}
}
impl<'source> NthToken for JsTokenSource<'source> {
#[inline(always)]
fn nth(&mut self, n: usize) -> JsSyntaxKind {
if n == 0 {
self.current()
} else {
self.lookahead(n).map_or(EOF, |lookahead| lookahead.kind)
}
}
#[inline(always)]
fn has_nth_preceding_line_break(&mut self, n: usize) -> bool {
if n == 0 {
self.has_preceding_line_break()
} else {
self.lookahead(n)
.map_or(false, |lookahead| lookahead.after_newline)
}
}
}