#[cfg(feature = "regex")]
use regex::Regex;
use super::{Position, TARGET};
#[derive(Clone, Copy, Debug)]
pub struct Input<'a> {
input: &'a str,
current: (Option<char>, Position, bool),
}
impl<'a> Input<'a> {
pub fn new(input: &'a str) -> Self {
let current = if let Some((_, ch)) = Self::decode(input) {
(Some(ch), Position::new(0, 1, 1), ch == '\n')
} else {
(None, Position::new(0, 1, 1), false)
};
log::debug!(target: TARGET, "Input: new input {:#}: {:?}", current.1, input);
Self { input, current }
}
#[inline]
pub const fn current(&self) -> Option<char> {
self.current.0
}
#[inline]
pub const fn position(&self) -> Position {
self.current.1
}
#[inline]
pub const fn newline(&self) -> bool {
self.current.2
}
#[inline]
pub fn advance(&mut self) -> Option<char> {
if let Some(curr_ch) = self.current.0 {
let offset = curr_ch.len_utf8();
self.input = &self.input[offset..];
if let Some((_, next_ch)) = Self::decode(self.input) {
let (curr_ch, pos, newline) = &mut self.current;
if *newline {
pos.column = 1;
pos.line += 1;
} else if *curr_ch != Some('\r') {
pos.column += 1;
}
*curr_ch = Some(next_ch);
*newline = next_ch == '\n';
pos.offset += offset as u32;
log::trace!(target: TARGET, "input: {:#}: {:?}", pos, next_ch);
return Some(next_ch);
} else {
let (ch, pos, _) = &mut self.current;
if *ch != Some('\r') {
pos.column += 1;
}
*ch = None;
pos.offset += offset as u32;
}
}
log::debug!(target: TARGET, "Input: {:#}: reached end of input", self.current.1);
None
}
pub fn advance_tag(&mut self, tag: &str) -> Option<bool> {
let state = *self;
for chr in tag.chars() {
if let Some(curr) = self.current() {
if curr != chr {
*self = state;
return Some(false);
}
self.advance();
} else {
*self = state;
return None;
}
}
Some(true)
}
#[cfg(feature = "regex")]
pub fn advance_match(&mut self, re: &Regex) -> Option<&'a str> {
if let Some(mat) = re.find(self.input) {
let matched = &self.input[..mat.end()];
for _ in matched.chars() {
self.advance();
}
Some(matched)
} else {
None
}
}
pub fn advance_if<F>(&mut self, f: F) -> Option<bool>
where
F: FnOnce(char) -> bool,
{
if let Some(chr) = self.current() {
if f(chr) {
self.advance();
return Some(true);
}
return Some(false);
}
None
}
pub fn advance_while<F>(&mut self, f: F) -> &'a str
where
F: Fn(char) -> bool,
{
let matched = self.input;
let start = self.position().offset();
while let Some(chr) = self.current() {
if !f(chr) {
break;
}
self.advance();
}
&matched[..(self.position().offset() - start)]
}
pub fn advance_until<F>(&mut self, f: F) -> Option<&'a str>
where
F: Fn(char) -> bool,
{
let state = *self;
let matched = self.input;
let start = self.position().offset();
while let Some(chr) = self.current() {
if f(chr) {
return Some(&matched[..(self.position().offset() - start)]);
}
self.advance();
}
*self = state;
None
}
#[inline]
pub const fn len(&self) -> usize {
self.input.len()
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.input.is_empty()
}
#[inline]
pub const fn is_eof(&self) -> bool {
self.current.0.is_none()
}
#[inline]
pub const fn as_str(&self) -> &'a str {
self.input
}
#[inline]
fn decode(input: &str) -> Option<(usize, char)> {
if let Some(ch) = input.chars().next() {
let offset = ch.len_utf8();
Some((offset, ch))
} else {
None
}
}
}
impl PartialEq for Input<'_> {
fn eq(&self, other: &Self) -> bool {
self.input == other.input
}
}
impl<'a> From<&'a str> for Input<'a> {
fn from(input: &'a str) -> Self {
Self::new(input)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn input_advance() {
let mut input = Input::new("abc\n123");
assert_eq!(input.current(), Some('a'));
assert_eq!(input.position(), Position::new(0, 1, 1));
assert!(!input.newline());
assert_eq!(input.advance(), Some('b'));
assert_eq!(input.current(), Some('b'));
assert_eq!(input.position(), Position::new(1, 1, 2));
assert!(!input.newline());
assert_eq!(input.advance(), Some('c'));
assert_eq!(input.current(), Some('c'));
assert_eq!(input.position(), Position::new(2, 1, 3));
assert!(!input.newline());
assert_eq!(input.advance(), Some('\n'));
assert_eq!(input.current(), Some('\n'));
assert_eq!(input.position(), Position::new(3, 1, 4));
assert!(input.newline());
assert_eq!(input.advance(), Some('1'));
assert_eq!(input.current(), Some('1'));
assert_eq!(input.position(), Position::new(4, 2, 1));
assert!(!input.newline());
assert_eq!(input.advance(), Some('2'));
assert_eq!(input.current(), Some('2'));
assert_eq!(input.position(), Position::new(5, 2, 2));
assert!(!input.newline());
assert_eq!(input.advance(), Some('3'));
assert_eq!(input.current(), Some('3'));
assert_eq!(input.position(), Position::new(6, 2, 3));
assert!(!input.newline());
assert_eq!(input.advance(), None);
assert_eq!(input.current(), None);
assert_eq!(input.position(), Position::new(7, 2, 4));
assert!(!input.newline());
assert_eq!(input.advance(), None);
assert_eq!(input.current(), None);
assert_eq!(input.position(), Position::new(7, 2, 4));
assert!(!input.newline());
}
#[test]
fn input_advance_if() {
let mut input = Input::new("abc\n123");
assert_eq!(input.current(), Some('a'));
assert_eq!(input.position(), Position::new(0, 1, 1));
assert!(!input.newline());
assert_eq!(input.advance_if(|c| c.is_ascii_alphabetic()), Some(true));
assert_eq!(input.current(), Some('b'));
assert_eq!(input.position(), Position::new(1, 1, 2));
assert!(!input.newline());
assert_eq!(input.advance_if(|c| c.is_ascii_alphabetic()), Some(true));
assert_eq!(input.current(), Some('c'));
assert_eq!(input.position(), Position::new(2, 1, 3));
assert!(!input.newline());
assert_eq!(input.advance_if(|c| c.is_ascii_alphabetic()), Some(true));
assert_eq!(input.current(), Some('\n'));
assert_eq!(input.position(), Position::new(3, 1, 4));
assert!(input.newline());
assert_eq!(input.advance_if(|c| c.is_ascii_alphabetic()), Some(false));
assert_eq!(input.current(), Some('\n'));
assert_eq!(input.position(), Position::new(3, 1, 4));
assert!(input.newline());
assert_eq!(input.advance(), Some('1'));
assert_eq!(input.current(), Some('1'));
assert_eq!(input.position(), Position::new(4, 2, 1));
assert!(!input.newline());
assert_eq!(input.advance_if(|c| c.is_ascii_digit()), Some(true));
assert_eq!(input.current(), Some('2'));
assert_eq!(input.position(), Position::new(5, 2, 2));
assert!(!input.newline());
assert_eq!(input.advance_if(|c| c.is_ascii_digit()), Some(true));
assert_eq!(input.current(), Some('3'));
assert_eq!(input.position(), Position::new(6, 2, 3));
assert!(!input.newline());
assert_eq!(input.advance_if(|c| c.is_ascii_digit()), Some(true));
assert_eq!(input.current(), None);
assert_eq!(input.position(), Position::new(7, 2, 4));
assert!(!input.newline());
assert_eq!(input.advance_if(|c| c.is_ascii_digit()), None);
assert_eq!(input.current(), None);
assert_eq!(input.position(), Position::new(7, 2, 4));
assert!(!input.newline());
}
#[test]
fn input_advance_while() {
let mut input = Input::new("abc\n123");
assert_eq!(input.current(), Some('a'));
assert_eq!(input.position(), Position::new(0, 1, 1));
assert!(!input.newline());
assert_eq!(input.advance_while(|c| c.is_ascii_alphabetic()), "abc");
assert_eq!(input.current(), Some('\n'));
assert_eq!(input.position(), Position::new(3, 1, 4));
assert!(input.newline());
assert_eq!(input.advance_while(|c| c.is_ascii_alphabetic()), "");
assert_eq!(input.current(), Some('\n'));
assert_eq!(input.position(), Position::new(3, 1, 4));
assert!(input.newline());
assert_eq!(input.advance(), Some('1'));
assert_eq!(input.current(), Some('1'));
assert_eq!(input.position(), Position::new(4, 2, 1));
assert!(!input.newline());
assert_eq!(input.advance_while(|c| c.is_ascii_digit()), "123");
assert_eq!(input.current(), None);
assert_eq!(input.position(), Position::new(7, 2, 4));
assert!(!input.newline());
assert_eq!(input.advance_while(|c| c.is_ascii_alphabetic()), "");
assert_eq!(input.current(), None);
assert_eq!(input.position(), Position::new(7, 2, 4));
assert!(!input.newline());
}
#[cfg(feature = "regex")]
#[test]
fn input_advance_match() {
let mut input = Input::new("abc\n123");
assert_eq!(input.current(), Some('a'));
assert_eq!(input.position(), Position::new(0, 1, 1));
assert!(!input.newline());
assert_eq!(input.advance_match(&Regex::new(r"^\w+").unwrap()), Some("abc"));
assert_eq!(input.current(), Some('\n'));
assert_eq!(input.position(), Position::new(3, 1, 4));
assert!(input.newline());
assert_eq!(input.advance_match(&Regex::new(r"^\w+").unwrap()), None);
assert_eq!(input.current(), Some('\n'));
assert_eq!(input.position(), Position::new(3, 1, 4));
assert!(input.newline());
assert_eq!(input.advance(), Some('1'));
assert_eq!(input.current(), Some('1'));
assert_eq!(input.position(), Position::new(4, 2, 1));
assert!(!input.newline());
assert_eq!(input.advance_match(&Regex::new(r"^\d+").unwrap()), Some("123"));
assert_eq!(input.current(), None);
assert_eq!(input.position(), Position::new(7, 2, 4));
assert!(!input.newline());
assert_eq!(input.advance_match(&Regex::new(r"^\w+").unwrap()), None);
assert_eq!(input.current(), None);
assert_eq!(input.position(), Position::new(7, 2, 4));
assert!(!input.newline());
}
}