use core::str::CharIndices;
use crate::lexer::Span;
use super::span::FilePosition;
pub struct Cursor<'a> {
chars: CharIndices<'a>,
start: usize,
start_chars: CharIndices<'a>,
file_pos: FilePosition,
current: usize,
}
impl<'a> Cursor<'a> {
pub fn new(text: &'a str) -> Self {
Self {
chars: text.char_indices(),
start: 0,
start_chars: text.char_indices(),
file_pos: FilePosition::default(),
current: 0,
}
}
pub fn step(&mut self) {
self.start = self.current;
self.file_pos.start_line = self.file_pos.end_line;
self.file_pos.start_col = self.file_pos.end_col;
self.start_chars = self.chars.clone();
}
pub fn is_finished(&self) -> bool {
self.chars.as_str().is_empty()
}
pub fn current_lexem(&self) -> &str {
let n = self.chars.offset() - self.start_chars.offset();
&self.start_chars.as_str()[..n]
}
pub fn get_span(&self) -> Span {
let offset = self.start_chars.offset();
Span {
offset,
len: self.chars.offset() - offset,
}
}
pub fn file_pos(&self) -> FilePosition {
self.file_pos
}
pub fn advance(&mut self) -> char {
let c = self.chars.next().map_or('\0', |(_, c)| c);
self.current += 1;
self.file_pos.end_col += 1;
if c == '\n' {
self.file_pos.end_line += 1;
self.file_pos.end_col = 1;
}
c
}
pub fn advance_while<F>(&mut self, f: F) -> bool
where
F: Fn(&char) -> bool,
{
while f(&self.peek()) {
self.advance();
if self.is_finished() {
return false;
}
}
true
}
pub fn peek(&self) -> char {
self.chars.clone().next().map_or('\0', |(_, c)| c)
}
pub fn peek_next(&self) -> char {
let mut iter = self.chars.clone();
iter.next();
iter.next().map_or('\0', |(_, c)| c)
}
pub fn match_next(&mut self, c: char) -> bool {
if self.peek() == c {
self.advance();
return true;
}
false
}
}
#[cfg(test)]
mod tests {
use super::Cursor;
#[test]
fn test() {
let text = "Hello world!";
let mut cursor = Cursor::new(text);
for c in text.chars() {
assert!(!cursor.is_finished());
let next = cursor.advance();
assert_eq!(next, c);
}
assert!(cursor.is_finished());
}
}