use std::collections::VecDeque;
use crate::language::{ parser::{ editor::EditorParser,
source::{ SyntaxContext, SyntaxFact, SyntaxJournal,
SyntaxMetadata, SyntaxRole } },
text::span::SourceSpan,
tokenizer::tokenizer::{ Token, TokenKind, Tokenizer, TokenizerError } };
#[derive(Clone, Copy)]
struct Transaction
{
position: usize,
syntax_checkpoint: usize,
last_read: Option<SourceSpan>,
}
pub struct TokenBuffer<'tokenizer, 'input>
{
tokenizer: &'tokenizer mut Tokenizer<'input>,
lookahead_buffer: Vec<Token>,
position: usize,
lookahead_stack: VecDeque<Transaction>,
pub(crate) syntax: Option<SyntaxJournal>,
context: SyntaxContext,
pub(crate) editor: Option<EditorParser>,
pub(crate) last_read: Option<SourceSpan>,
}
const LOOKAHEAD_BUFFER_CAPACITY: usize = 50;
impl<'tokenizer, 'input> TokenBuffer<'tokenizer, 'input>
{
pub fn new(tokenizer: &'tokenizer mut Tokenizer<'input>) -> Self
{
Self
{
tokenizer,
lookahead_buffer: Vec::with_capacity(LOOKAHEAD_BUFFER_CAPACITY),
position: 0,
lookahead_stack: VecDeque::with_capacity(LOOKAHEAD_BUFFER_CAPACITY),
syntax: None,
context: SyntaxContext::Expression,
editor: None,
last_read: None,
}
}
pub fn with_syntax(tokenizer: &'tokenizer mut Tokenizer<'input>) -> Self
{
let mut buffer = Self::new(tokenizer);
buffer.syntax = Some(SyntaxJournal::default());
buffer
}
pub(crate) fn for_editor(tokenizer: &'tokenizer mut Tokenizer<'input>) -> Self
{
let mut buffer = Self::with_syntax(tokenizer);
buffer.editor = Some(EditorParser::new());
buffer
}
pub(crate) fn is_editor(&self) -> bool { self.editor.is_some() }
pub(crate) fn editor_offset(&self) -> usize { self.tokenizer.byte_offset() }
pub fn syntax_metadata(&self) -> Option<SyntaxMetadata>
{ self.syntax.as_ref().map(SyntaxJournal::snapshot) }
pub(crate) fn syntax_checkpoint(&self) -> usize
{ self.syntax.as_ref().map_or(0, |journal| journal.entries.len()) }
pub(crate) fn with_context<T>(&mut self, context: SyntaxContext,
parse: impl FnOnce(&mut Self) -> T) -> T
{
let previous = self.context;
self.context = context;
let result = parse(self);
self.context = previous;
result
}
pub(crate) fn annotate(&mut self, span: SourceSpan, role: SyntaxRole)
{
if let Some(journal) = &mut self.syntax
{ journal.entries.push(SyntaxFact { span, role, context: self.context }); }
}
pub(crate) fn annotate_word(&mut self, span: SourceSpan)
{
self.annotate(span, self.word_role());
}
fn word_role(&self) -> SyntaxRole
{
match self.context
{
SyntaxContext::Argument => SyntaxRole::Argument,
SyntaxContext::Command => SyntaxRole::CommandName,
SyntaxContext::Type => SyntaxRole::TypeName,
SyntaxContext::Expression => SyntaxRole::Name,
}
}
pub(crate) fn annotate_names_since(&mut self, checkpoint: usize, role: SyntaxRole)
{
if let Some(journal) = &mut self.syntax
{
let end = journal.entries.len();
for index in checkpoint..end
{
let fact = journal.entries[index];
if matches!(fact.role, SyntaxRole::Name | SyntaxRole::CommandName
| SyntaxRole::Argument | SyntaxRole::TypeName)
{ journal.entries.push(SyntaxFact { role, ..fact }); }
}
}
}
fn record_token(&mut self, token: &Token)
{
if self.syntax.is_none() { return; }
if self.editor.as_ref().is_some_and(|editor| editor.recovering) { return; }
let role = match token.kind
{
TokenKind::Invalid => SyntaxRole::Invalid,
TokenKind::Symbol => self.word_role(),
TokenKind::Identifier | TokenKind::AutoIdentifier => SyntaxRole::Variable,
TokenKind::Member => SyntaxRole::Member,
TokenKind::Literal => SyntaxRole::Literal,
TokenKind::Let | TokenKind::Export | TokenKind::Public | TokenKind::Alias
| TokenKind::Sub | TokenKind::If | TokenKind::Else | TokenKind::While
| TokenKind::Until | TokenKind::For | TokenKind::In | TokenKind::Break
| TokenKind::Continue | TokenKind::Loop | TokenKind::Match | TokenKind::Return
| TokenKind::Function | TokenKind::Struct | TokenKind::Enum | TokenKind::Type
| TokenKind::Import => SyntaxRole::Keyword,
TokenKind::Comma | TokenKind::BlockOpen | TokenKind::BlockClose | TokenKind::EmptyBlock
| TokenKind::SquareOpen | TokenKind::SquareClose | TokenKind::IndexOpen
| TokenKind::ParenOpen | TokenKind::ParenClose | TokenKind::StatementBreak
| TokenKind::LineBreak | TokenKind::LineContinue => SyntaxRole::Delimiter,
_ => SyntaxRole::Operator,
};
self.annotate(token.span, role);
}
pub fn mark_lookahead(&mut self)
{
let lookahead = if self.is_peeking()
{
self.lookahead_stack.front().map(|frame| frame.position)
}
else
{
Some(self.position)
};
if let Some(lookahead) = lookahead
{
self.lookahead_stack.push_front(Transaction
{ position: lookahead, syntax_checkpoint: self.syntax_checkpoint(),
last_read: self.last_read });
}
}
pub fn commit_lookahead(&mut self)
{
if let Some(lookahead) = self.lookahead_stack.pop_front()
{
if self.is_peeking()
{
self.lookahead_stack[0].position = lookahead.position;
}
else
{
self.position = lookahead.position;
self.clear_finished_buffer();
}
}
}
pub fn rollback_lookahead(&mut self)
{
if let Some(frame) = self.lookahead_stack.pop_front()
{
self.last_read = frame.last_read;
if let Some(journal) = &mut self.syntax
{ journal.entries.truncate(frame.syntax_checkpoint); }
self.clear_finished_buffer();
}
}
pub fn next(&mut self) -> Result<Option<Token>, TokenizerError>
{
if let Some(editor) = &mut self.editor
{
if editor.steps == 0
{
editor.exhausted = true;
return Err(self.editor_limit_error());
}
editor.steps -= 1;
}
let index = self.position();
if index >= self.lookahead_buffer.len()
{
match self.tokenizer.next_token()?
{
Some(token) => self.lookahead_buffer.push(token),
None =>
{
if self.is_editor()
{
let end = self.tokenizer.byte_offset();
self.last_read = Some(SourceSpan::new(end, end));
}
return Ok(None);
}
}
}
let token = self.lookahead_buffer[index].clone();
if self.is_editor() { self.last_read = Some(token.span); }
if let Some(cursor) = self.lookahead_stack.front_mut()
{
cursor.position += 1;
}
else
{
self.position += 1;
self.clear_finished_buffer();
}
self.record_token(&token);
Ok(Some(token))
}
pub(crate) fn position(&self) -> usize
{
self.lookahead_stack.front().map_or(self.position, |frame| frame.position)
}
fn is_peeking(&self) -> bool
{
!self.lookahead_stack.is_empty()
}
fn clear_finished_buffer(&mut self)
{
if self.position >= self.lookahead_buffer.len()
&& !self.is_peeking()
{
self.lookahead_buffer.clear();
self.position = 0;
}
}
}