use std::borrow::Cow;
use std::cmp::max;
use crate::error::LexError;
use crate::lexer::SpannedResult;
use logos::{Logos, Span};
use partiql_common::syntax::line_offset_tracker::LineOffsetTracker;
use partiql_common::syntax::location::ByteOffset;
type CommentStringResult<'input> = SpannedResult<&'input str, ByteOffset, LexError<'input>>;
#[derive(Logos, Debug, Clone, PartialEq, Eq)]
#[logos(skip r"[^/*\r\n\u0085\u2028\u2029]+")]
enum CommentToken {
#[regex(r"[/*]", logos::skip)]
Any,
#[regex(r"(([\r])?[\n])|\u0085|\u2028|\u2029")]
Newline,
#[token("*/")]
End,
#[token("/*")]
Start,
}
pub struct CommentLexer<'input, 'tracker> {
lexer: logos::Lexer<'input, CommentToken>,
comment_nesting: bool,
tracker: &'tracker mut LineOffsetTracker,
}
impl<'input, 'tracker> CommentLexer<'input, 'tracker> {
#[inline]
pub fn new(input: &'input str, tracker: &'tracker mut LineOffsetTracker) -> Self {
CommentLexer {
lexer: CommentToken::lexer(input),
comment_nesting: false,
tracker,
}
}
#[inline]
pub fn with_nesting(mut self) -> Self {
self.comment_nesting = true;
self
}
#[inline]
fn err_here(
&self,
err_ctor: fn(Cow<'input, str>) -> LexError<'input>,
) -> CommentStringResult<'input> {
let Span { start, .. } = self.lexer.span();
self.err_ends_here(start, err_ctor)
}
#[inline]
fn err_ends_here(
&self,
start: usize,
err_ctor: fn(Cow<'input, str>) -> LexError<'input>,
) -> CommentStringResult<'input> {
let region = self.lexer.slice();
let Span { end, .. } = self.lexer.span();
Err((start.into(), err_ctor(region.into()), end.into()))
}
fn next_internal(&mut self) -> Option<CommentStringResult<'input>> {
let Span { start, .. } = self.lexer.span();
let mut nesting = 0;
let nesting_inc = i32::from(self.comment_nesting);
'comment: loop {
match self.lexer.next() {
Some(Ok(CommentToken::Any)) => continue,
Some(Ok(CommentToken::Newline)) => {
self.tracker.record(self.lexer.span().end.into());
}
Some(Ok(CommentToken::Start)) => nesting = max(1, nesting + nesting_inc),
Some(Ok(CommentToken::End)) => {
if nesting == 0 {
return Some(self.err_here(|_| LexError::UnterminatedComment));
}
nesting -= 1;
if nesting == 0 {
break 'comment;
}
}
Some(Err(_)) => return Some(self.err_here(LexError::InvalidInput)),
None => {
let result = if nesting != 0 {
Some(self.err_ends_here(start, |_| LexError::UnterminatedComment))
} else {
None
};
return result;
}
}
}
let Span { end, .. } = self.lexer.span();
let comment = &self.lexer.source()[start..end];
Some(Ok((start.into(), comment, end.into())))
}
}
impl<'input> Iterator for CommentLexer<'input, '_> {
type Item = CommentStringResult<'input>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
self.next_internal()
}
}