use std::cmp::min;
use crate::ast::Pos;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Reader {
pub buffer: Vec<char>,
pub state: ReaderState,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ReaderState {
pub cursor: usize,
pub pos: Pos,
}
impl Reader {
pub fn new(s: &str) -> Reader {
Reader {
buffer: s.chars().collect(),
state: ReaderState {
cursor: 0,
pos: Pos { line: 1, column: 1 },
},
}
}
pub fn is_eof(&self) -> bool {
self.state.cursor == self.buffer.len()
}
pub fn read(&mut self) -> Option<char> {
match self.buffer.get(self.state.cursor) {
None => None,
Some(c) => {
self.state.cursor += 1;
if !is_combining_character(*c) {
self.state.pos.column += 1;
}
if *c == '\n' {
self.state.pos.column = 1;
self.state.pos.line += 1;
}
Some(*c)
}
}
}
pub fn read_n(&mut self, count: usize) -> String {
let mut s = String::new();
for _ in 0..count {
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() {
None => return s,
Some(c) => {
if predicate(&c) {
s.push(self.read().unwrap())
} else {
return s;
}
}
}
}
}
pub fn read_while_escaping(&mut self, predicate: fn(&char) -> bool) -> String {
let mut s = String::new();
let mut escaped = false;
loop {
match self.peek() {
None => return s,
Some(c) => {
if escaped && c == ' ' {
escaped = false;
s.push(self.read().unwrap())
} else if c == '\\' {
escaped = true;
let _backslash = self.read().unwrap();
} else if predicate(&c) {
s.push(self.read().unwrap())
} else {
return s;
}
}
}
}
}
pub fn peek(&mut self) -> Option<char> {
self.buffer.get(self.state.cursor).copied()
}
pub fn peek_ignoring_whitespace(&mut self) -> Option<char> {
let mut i = self.state.cursor;
loop {
if let Some(c) = self.buffer.get(i).copied() {
if c != ' ' && c != '\t' && c != '\n' && c != '\r' {
return Some(c);
}
} else {
return None;
}
i += 1;
}
}
pub fn peek_n(&self, count: usize) -> String {
let start = self.state.cursor;
let end = min(start + count, self.buffer.len());
self.buffer[start..end].iter().collect()
}
pub fn try_literal(&mut self, value: &str) -> bool {
if self.peek_n(value.len()) == value {
self.read_n(value.len());
true
} else {
false
}
}
pub fn peek_back(&self, start: usize) -> String {
let end = self.state.cursor;
self.buffer[start..end].iter().collect()
}
}
fn is_combining_character(c: char) -> bool {
c > '\u{0300}' && c < '\u{036F}' }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reader() {
let mut reader = Reader::new("hi");
assert_eq!(reader.state.cursor, 0);
assert!(!reader.is_eof());
assert_eq!(reader.peek_n(2), "hi".to_string());
assert_eq!(reader.read().unwrap(), 'h');
assert_eq!(reader.state.cursor, 1);
assert_eq!(reader.peek().unwrap(), 'i');
assert_eq!(reader.state.cursor, 1);
assert_eq!(reader.read().unwrap(), 'i');
assert!(reader.is_eof());
assert_eq!(reader.read(), None);
}
#[test]
fn test_try_predicate() {
let mut reader = Reader::new("hi");
assert!(reader.try_literal("hi"));
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::new("hello");
assert!(!reader.try_literal("hi"));
assert_eq!(reader.state.cursor, 0);
}
}