use super::LexError;
use crate::Span;
use crate::span::fallback;
#[derive(Copy, Clone)]
pub struct Cursor<'a> {
rest: &'a str,
off: u32,
}
impl<'a> Cursor<'a> {
pub fn new(src: &'a str, offset: u32) -> Self {
Self { rest: src, off: offset }
}
pub fn rest(&self) -> &'a str {
self.rest
}
pub fn offset(&self) -> u32 {
self.off
}
pub fn is_empty(&self) -> bool {
self.rest.is_empty()
}
pub fn first(&self) -> Option<char> {
self.rest.chars().next()
}
pub fn starts_with(&self, s: &str) -> bool {
self.rest.starts_with(s)
}
pub fn span_to(&self, end: &Cursor<'_>) -> Span {
fallback::Span::new(self.off, end.off).into()
}
pub fn span(&self) -> Span {
fallback::Span::new(self.off, self.off + 1).into()
}
pub fn error(&self) -> LexError {
LexError::new(self.span())
}
pub fn advance(&self, n: usize) -> Self {
Self {
rest: &self.rest[n..],
off: self.off + n as u32,
}
}
pub fn skip_while(&self, mut pred: impl FnMut(char) -> bool) -> Self {
let mut bytes = 0;
for ch in self.rest.chars() {
if !pred(ch) {
break;
}
bytes += ch.len_utf8();
}
self.advance(bytes)
}
pub fn skip_whitespace(mut self) -> Self {
loop {
let next = self.skip_while(|ch| ch.is_whitespace());
if next.offset() != self.offset() {
self = next;
continue;
}
if self.starts_with("//") && !self.is_line_doc() {
self = self.skip_while(|ch| ch != '\n');
if self.starts_with("\n") {
self = self.advance(1);
}
continue;
}
if self.starts_with("/*") && !self.is_block_doc() {
match self.skip_comment() {
None => break, Some(next) => {
self = next;
continue;
}
}
}
break;
}
self
}
pub fn is_line_doc(&self) -> bool {
(self.starts_with("///") && !self.starts_with("////")) || self.starts_with("//!")
}
pub fn is_block_doc(&self) -> bool {
self.starts_with("/*!") || (self.starts_with("/**") && !self.starts_with("/***") && !self.starts_with("/**/"))
}
pub fn doc_comment(&self) -> Option<(Self, bool, String)> {
if self.is_line_doc() {
let inner = self.starts_with("//!");
let body = self.advance(3); let end = body.skip_while(|ch| ch != '\n');
let text: String = body.rest()[..(end.offset() - body.offset()) as usize].to_string();
let next = if end.starts_with("\n") { end.advance(1) } else { end };
return Some((next, inner, text.trim().to_string()));
}
if self.is_block_doc() {
let inner = self.starts_with("/*!");
let body = self.advance(3); let close = body.skip_comment_to_close()?;
let len = (close.offset() - body.offset()) as usize - 2;
let text: String = body.rest()[..len].to_string();
return Some((close, inner, text.trim().to_string()));
}
None
}
fn skip_comment_to_close(&self) -> Option<Self> {
let mut cur = *self;
while !cur.is_empty() {
if cur.starts_with("*/") {
return Some(cur.advance(2));
}
let ch = cur.first().unwrap();
cur = cur.advance(ch.len_utf8());
}
None
}
pub fn skip_comment(&self) -> Option<Self> {
let mut cur = self.advance(2); let mut depth = 1u32;
while !cur.is_empty() {
if cur.starts_with("/*") {
depth += 1;
cur = cur.advance(2);
} else if cur.starts_with("*/") {
depth -= 1;
cur = cur.advance(2);
if depth == 0 {
return Some(cur);
}
} else {
let ch = cur.first().unwrap();
cur = cur.advance(ch.len_utf8());
}
}
None
}
}