use crate::TextSize;
use std::str::Chars;
pub(crate) struct Cursor<'s> {
text: &'s str,
len: TextSize,
}
impl<'s> Cursor<'s> {
pub fn new(text: &'s str) -> Cursor<'s> {
Cursor {
text,
len: 0.into(),
}
}
pub fn into_len(self) -> TextSize {
self.len
}
pub fn current(&self) -> Option<char> {
self.chars().next()
}
pub fn nth(&self, n: u32) -> Option<char> {
self.chars().nth(n as usize)
}
pub fn matches(&self, c: char) -> bool {
self.current() == Some(c)
}
pub fn matches_str(&self, s: &str) -> bool {
let chars = self.chars();
chars.as_str().starts_with(s)
}
pub fn matches_nth_if<F: Fn(char) -> bool>(&self, n: u32, predicate: F) -> bool {
self.nth(n).map(predicate) == Some(true)
}
pub fn bump(&mut self) -> Option<char> {
let ch = self.chars().next()?;
self.len += TextSize::of(ch);
Some(ch)
}
pub fn bump_while<F: Fn(char) -> bool>(&mut self, predicate: F) {
loop {
match self.current() {
Some(c) if predicate(c) => {
self.bump();
}
_ => return,
}
}
}
pub fn current_token_text(&self) -> &str {
let len: u32 = self.len.into();
&self.text[..len as usize]
}
fn chars(&self) -> Chars {
let len: u32 = self.len.into();
self.text[len as usize..].chars()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_current() {
let cursor = Cursor::new("hello");
assert_eq!(cursor.current(), Some('h'));
}
#[test]
fn test_nth() {
let cursor = Cursor::new("hello");
assert_eq!(cursor.nth(0), Some('h'));
assert_eq!(cursor.nth(1), Some('e'));
assert_eq!(cursor.nth(2), Some('l'));
assert_eq!(cursor.nth(3), Some('l'));
assert_eq!(cursor.nth(4), Some('o'));
assert_eq!(cursor.nth(5), None);
}
#[test]
fn test_matches() {
let cursor = Cursor::new("hello");
assert!(cursor.matches('h'));
assert!(!cursor.matches('t'));
}
#[test]
fn test_matches_str() {
let cursor = Cursor::new("hello");
assert!(cursor.matches_str("h"));
assert!(cursor.matches_str("he"));
assert!(cursor.matches_str("hel"));
assert!(cursor.matches_str("hello"));
assert!(!cursor.matches_str("world"));
}
#[test]
fn test_matches_nth_if() {
let cursor = Cursor::new("hello");
assert!(cursor.matches_nth_if(0, |c| c == 'h'));
assert!(!cursor.matches_nth_if(1, |c| c == 'h'));
assert!(cursor.matches_nth_if(4, |c| c == 'o'));
assert!(!cursor.matches_nth_if(400, |c| c == 'h'));
}
#[test]
fn test_bump() {
let mut cursor = Cursor::new("hello");
assert_eq!(cursor.current(), Some('h'));
cursor.bump();
assert_eq!(cursor.current(), Some('e'));
cursor.bump();
assert_eq!(cursor.current(), Some('l'));
cursor.bump();
assert_eq!(cursor.current(), Some('l'));
cursor.bump();
assert_eq!(cursor.current(), Some('o'));
cursor.bump();
assert_eq!(cursor.current(), None);
cursor.bump();
assert_eq!(cursor.current(), None);
}
#[test]
fn test_bump_while() {
let mut cursor = Cursor::new("hello");
assert_eq!(cursor.current(), Some('h'));
cursor.bump_while(|c| c != 'o');
assert_eq!(cursor.current(), Some('o'));
}
}