use crate::span::Span;
use memchr::memchr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Line<'a> {
pub text: &'a str,
pub span: Span,
}
impl<'a> Line<'a> {
#[inline(always)]
pub fn is_blank(&self) -> bool {
self.text.bytes().all(|b| b == b' ' || b == b'\t')
}
#[inline(always)]
pub fn starts_with(&self, prefix: &str) -> bool {
self.text.as_bytes().starts_with(prefix.as_bytes())
}
#[inline(always)]
pub fn trimmed(&self) -> &str {
self.text.trim()
}
#[inline(always)]
pub fn strip_prefix(&self, prefix: &str) -> Option<&'a str> {
self.text.strip_prefix(prefix)
}
}
pub struct Lexer<'a> {
input: &'a str,
bytes: &'a [u8],
offset: usize,
peeked: Option<Line<'a>>,
}
impl<'a> Lexer<'a> {
#[inline]
pub fn new(input: &'a str) -> Self {
Self {
input,
bytes: input.as_bytes(),
offset: 0,
peeked: None,
}
}
#[inline(always)]
pub fn offset(&self) -> u32 {
self.offset as u32
}
#[inline(always)]
pub fn is_eof(&self) -> bool {
self.peeked.is_none() && self.offset >= self.bytes.len()
}
#[inline]
pub fn peek_line(&mut self) -> Option<&Line<'a>> {
if self.peeked.is_none() {
self.peeked = self.read_line();
}
self.peeked.as_ref()
}
#[inline]
pub fn next_line(&mut self) -> Option<Line<'a>> {
if let Some(line) = self.peeked.take() {
return Some(line);
}
self.read_line()
}
#[inline]
pub fn skip_blank_lines(&mut self) -> usize {
let mut count = 0;
while let Some(line) = self.peek_line() {
if !line.is_blank() {
break;
}
self.next_line();
count += 1;
}
count
}
#[inline(always)]
fn read_line(&mut self) -> Option<Line<'a>> {
if self.offset >= self.bytes.len() {
return None;
}
let start = self.offset;
let end = match memchr(b'\n', &self.bytes[start..]) {
Some(pos) => start + pos,
None => self.bytes.len(),
};
let text_end = if end > start && self.bytes[end - 1] == b'\r' {
end - 1
} else {
end
};
self.offset = if end < self.bytes.len() { end + 1 } else { end };
Some(Line {
text: unsafe { self.input.get_unchecked(start..text_end) },
span: Span::new(start as u32, text_end as u32),
})
}
#[inline(always)]
pub fn slice(&self, span: Span) -> &'a str {
unsafe {
self.input
.get_unchecked(span.start as usize..span.end as usize)
}
}
#[inline(always)]
pub fn remaining(&self) -> &'a str {
unsafe { self.input.get_unchecked(self.offset..) }
}
}