use std::ops::{Add, AddAssign, Sub};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Reader {
buf: Vec<char>,
cursor: Cursor,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Pos {
pub line: usize,
pub column: usize,
}
impl Pos {
pub fn new(line: usize, column: usize) -> Pos {
Pos { line, column }
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct CharPos(pub usize);
impl Sub for CharPos {
type Output = CharPos;
#[inline(always)]
fn sub(self, rhs: Self) -> Self::Output {
CharPos(self.0 - rhs.0)
}
}
impl Add for CharPos {
type Output = CharPos;
#[inline(always)]
fn add(self, rhs: Self) -> Self::Output {
CharPos(self.0 + rhs.0)
}
}
impl AddAssign for CharPos {
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Cursor {
pub index: CharPos,
pub pos: Pos,
}
impl Reader {
pub fn new(s: &str) -> Self {
Reader {
buf: s.chars().collect(),
cursor: Cursor {
index: CharPos(0),
pos: Pos { line: 1, column: 1 },
},
}
}
pub fn with_pos(s: &str, pos: Pos) -> Self {
Reader {
buf: s.chars().collect(),
cursor: Cursor {
index: CharPos(0),
pos,
},
}
}
pub fn cursor(&self) -> Cursor {
self.cursor
}
pub fn seek(&mut self, to: Cursor) {
self.cursor = to;
}
pub fn is_eof(&self) -> bool {
self.cursor.index.0 == self.buf.len()
}
pub fn read(&mut self) -> Option<char> {
match self.buf.get(self.cursor.index.0) {
None => None,
Some(c) => {
self.cursor.index += CharPos(1);
if !is_combining_character(*c) {
self.cursor.pos.column += 1;
}
if *c == '\n' {
self.cursor.pos.column = 1;
self.cursor.pos.line += 1;
}
Some(*c)
}
}
}
pub fn read_n(&mut self, count: CharPos) -> String {
let mut s = String::new();
for _ in 0..count.0 {
match self.read() {
None => {}
Some(c) => s.push(c),
}
}
s
}
pub fn read_while(&mut self, predicate: fn(char) -> bool) -> String {
let mut s = String::new();
loop {
match self.peek() {
Some(c) if predicate(c) => {
_ = self.read();
s.push(c);
}
_ => return s,
}
}
}
pub fn read_from(&self, start: CharPos) -> String {
let end = self.cursor.index;
self.buf[start.0..end.0].iter().collect()
}
pub fn peek(&self) -> Option<char> {
self.buf.get(self.cursor.index.0).copied()
}
pub fn peek_if(&self, predicate: fn(char) -> bool) -> Option<char> {
let mut i = self.cursor.index;
loop {
let &c = self.buf.get(i.0)?;
if predicate(c) {
return Some(c);
}
i += CharPos(1);
}
}
pub fn peek_n(&self, count: usize) -> String {
let start = self.cursor.index;
let end = (start + CharPos(count)).min(CharPos(self.buf.len()));
self.buf[start.0..end.0].iter().collect()
}
}
fn is_combining_character(c: char) -> bool {
c > '\u{0300}' && c < '\u{036F}' }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_reader() {
let mut reader = Reader::new("hi");
assert_eq!(reader.cursor().index, CharPos(0));
assert!(!reader.is_eof());
assert_eq!(reader.peek_n(2), "hi".to_string());
assert_eq!(reader.cursor().index, CharPos(0));
assert_eq!(reader.read().unwrap(), 'h');
assert_eq!(reader.cursor().index, CharPos(1));
assert_eq!(reader.peek().unwrap(), 'i');
assert_eq!(reader.cursor().index, CharPos(1));
assert_eq!(reader.read().unwrap(), 'i');
assert!(reader.is_eof());
assert_eq!(reader.read(), None);
}
#[test]
fn peek_back() {
let mut reader = Reader::new("abcdefgh");
assert_eq!(reader.read(), Some('a'));
assert_eq!(reader.read(), Some('b'));
assert_eq!(reader.read(), Some('c'));
assert_eq!(reader.read(), Some('d'));
assert_eq!(reader.read(), Some('e'));
assert_eq!(reader.peek(), Some('f'));
assert_eq!(reader.read_from(CharPos(3)), "de");
}
#[test]
fn read_while() {
let mut reader = Reader::new("123456789");
assert_eq!(reader.read_while(|c| c.is_numeric()), "123456789");
assert_eq!(reader.cursor().index, CharPos(9));
assert!(reader.is_eof());
let mut reader = Reader::new("123456789abcde");
assert_eq!(reader.read_while(|c| c.is_numeric()), "123456789");
assert_eq!(reader.cursor().index, CharPos(9));
assert!(!reader.is_eof());
let mut reader = Reader::new("abcde123456789");
assert_eq!(reader.read_while(|c| c.is_numeric()), "");
assert_eq!(reader.cursor().index, CharPos(0));
}
#[test]
fn reader_create_with_from_pos() {
let mut main_reader = Reader::new("aaabb");
_ = main_reader.read();
_ = main_reader.read();
_ = main_reader.read();
let pos = main_reader.cursor().pos;
let s = main_reader.read_while(|_| true);
let mut sub_reader = Reader::with_pos(&s, pos);
assert_eq!(
sub_reader.cursor,
Cursor {
index: CharPos(0),
pos: Pos::new(1, 4)
}
);
_ = sub_reader.read();
assert_eq!(
sub_reader.cursor,
Cursor {
index: CharPos(1),
pos: Pos::new(1, 5)
}
);
}
#[test]
fn peek_ignoring_whitespace() {
fn is_whitespace(c: char) -> bool {
c == ' ' || c == '\t'
}
let reader = Reader::new("\t\t\tabc");
assert_eq!(reader.peek_if(|c| !is_whitespace(c)), Some('a'));
let reader = Reader::new("foo");
assert_eq!(reader.peek_if(|c| !is_whitespace(c)), Some('f'));
}
}