parent
e58a34820e
commit
b0532856d5
@ -0,0 +1,9 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type TapeResult<T> = Result<T, TapeError>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TapeError {
|
||||
#[error("IO Error: {0}")]
|
||||
TokioIoError(#[from] tokio::io::Error),
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
use crate::error::TapeResult;
|
||||
use crate::traits::AsyncReadSeek;
|
||||
use std::io::SeekFrom;
|
||||
use tokio::io::{AsyncReadExt, BufReader};
|
||||
use tokio::io::{AsyncSeekExt, ErrorKind};
|
||||
|
||||
/// An Input reader to asynchronously read a type
|
||||
/// that implements AsyncBufRead and AsyncSeek.
|
||||
pub struct InputReader {
|
||||
inner: BufReader<Box<dyn AsyncReadSeek>>,
|
||||
}
|
||||
|
||||
impl InputReader {
|
||||
pub fn new<T: AsyncReadSeek + 'static>(inner: T) -> Self {
|
||||
Self {
|
||||
inner: BufReader::new(Box::new(inner)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the next char consuming it in the process
|
||||
pub async fn consume(&mut self) -> TapeResult<char> {
|
||||
self.read_next().await
|
||||
}
|
||||
|
||||
/// Returns the next char without forwarding
|
||||
pub async fn peek(&mut self) -> TapeResult<char> {
|
||||
let char = self.read_next().await?;
|
||||
self.inner.get_mut().seek(SeekFrom::Current(-1)).await?;
|
||||
|
||||
Ok(char)
|
||||
}
|
||||
|
||||
/// Returns if EOF has been reached
|
||||
pub async fn check_eof(&mut self) -> TapeResult<bool> {
|
||||
let char = self.read_next().await?;
|
||||
|
||||
Ok(char == '\x00')
|
||||
}
|
||||
|
||||
/// Reads the next char returning \x00 for EOF
|
||||
async fn read_next(&mut self) -> TapeResult<char> {
|
||||
let mut value = String::with_capacity(1);
|
||||
|
||||
match self.inner.read_to_string(&mut value).await {
|
||||
Ok(_) => Ok(value.chars().next().unwrap()),
|
||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => Ok('\x00'),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,82 +1,3 @@
|
||||
pub mod tapemachine;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tapemachine::TapeResult;
|
||||
use crate::tapemachine::{CharTapeMachine, TapeError};
|
||||
|
||||
const TEST_STRING: &str = "TEST STRING 1234 \\l \\n";
|
||||
|
||||
#[test]
|
||||
fn it_returns_the_next_char() {
|
||||
let mut ctm = CharTapeMachine::new(TEST_STRING.chars().collect());
|
||||
let test_chars: Vec<char> = TEST_STRING.chars().collect();
|
||||
|
||||
let mut next = ctm.next_char().unwrap();
|
||||
assert_eq!(next, *test_chars.get(1).unwrap());
|
||||
|
||||
next = ctm.next_char().unwrap();
|
||||
assert_eq!(next, *test_chars.get(2).unwrap());
|
||||
|
||||
let _ = ctm.next_char().unwrap();
|
||||
let _ = ctm.next_char().unwrap();
|
||||
let _ = ctm.next_char().unwrap();
|
||||
next = ctm.next_char().unwrap();
|
||||
assert_eq!(next, *test_chars.get(6).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_rewinds() {
|
||||
let mut ctm = CharTapeMachine::new(TEST_STRING.chars().collect());
|
||||
let test_chars: Vec<char> = TEST_STRING.chars().collect();
|
||||
|
||||
ctm.next_char().unwrap();
|
||||
ctm.next_char().unwrap();
|
||||
assert_eq!(ctm.next_char(), Some(*test_chars.get(3).unwrap()));
|
||||
|
||||
ctm.rewind(1);
|
||||
assert_eq!(ctm.next_char(), Some(*test_chars.get(2).unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_seeks() {
|
||||
let mut ctm = CharTapeMachine::new(TEST_STRING.chars().collect());
|
||||
let test_chars: Vec<char> = TEST_STRING.chars().collect();
|
||||
|
||||
assert_eq!(ctm.next_char(), Some(*test_chars.get(1).unwrap()));
|
||||
ctm.seek_one().unwrap();
|
||||
assert_eq!(ctm.next_char(), Some(*test_chars.get(3).unwrap()));
|
||||
ctm.seek_one().unwrap();
|
||||
ctm.seek_whitespace();
|
||||
assert_eq!(ctm.next_char(), Some(*test_chars.get(6).unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_asserts_chars() -> TapeResult<()> {
|
||||
let mut ctm = CharTapeMachine::new(TEST_STRING.chars().collect());
|
||||
ctm.assert_any(&['A', 'B', 'T'], None)?;
|
||||
ctm.seek_one().unwrap();
|
||||
ctm.assert_char(&'E', None)?;
|
||||
ctm.seek_one().unwrap();
|
||||
ctm.assert_str_sequence("ST ", None)?;
|
||||
ctm.seek_one().unwrap();
|
||||
ctm.assert_any_sequence(&[&['C'], &['A'], &['A', 'B'], &['S', 'T', 'R']], None)?;
|
||||
|
||||
if let Ok(_) =
|
||||
ctm.assert_any_sequence(&[&['C'], &['A'], &['A', 'B'], &['S', 'T', 'R']], None)
|
||||
{
|
||||
Err(TapeError::new(0))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_checks_eof() -> TapeResult<()> {
|
||||
let mut ctm = CharTapeMachine::new(TEST_STRING.chars().collect());
|
||||
let _ = ctm.get_string_until_any(&['n'], &[]);
|
||||
assert!(ctm.check_eof());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub mod error;
|
||||
pub mod input;
|
||||
mod traits;
|
||||
|
@ -1,424 +0,0 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TapeError {
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl TapeError {
|
||||
pub fn new(index: usize) -> Self {
|
||||
Self { index }
|
||||
}
|
||||
|
||||
/// Returns the index the error occured on
|
||||
pub fn get_index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TapeError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Tape Error at: {}", self.index)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for TapeError {}
|
||||
|
||||
pub type TapeResult<T> = Result<T, TapeError>;
|
||||
|
||||
const ESCAPE: char = '\\';
|
||||
|
||||
pub struct CharTapeMachine {
|
||||
index: usize,
|
||||
text: Vec<char>,
|
||||
current_char: char,
|
||||
previous_char: char,
|
||||
}
|
||||
|
||||
impl CharTapeMachine {
|
||||
pub fn new(text: Vec<char>) -> Self {
|
||||
let current_char = if text.len() > 0 {
|
||||
*text.first().unwrap()
|
||||
} else {
|
||||
' '
|
||||
};
|
||||
Self {
|
||||
text,
|
||||
index: 0,
|
||||
previous_char: current_char,
|
||||
current_char,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_text(&self) -> Vec<char> {
|
||||
self.text.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
|
||||
/// returns the current char
|
||||
#[inline]
|
||||
pub fn get_current(&self) -> char {
|
||||
self.current_char
|
||||
}
|
||||
|
||||
/// Creates an error at the current position
|
||||
#[inline]
|
||||
pub fn err(&self) -> TapeError {
|
||||
TapeError::new(self.index)
|
||||
}
|
||||
|
||||
/// Returns the next char
|
||||
/// if there is any
|
||||
pub fn next_char(&mut self) -> Option<char> {
|
||||
if self.index < self.text.len() {
|
||||
self.index += 1;
|
||||
self.previous_char = self.current_char;
|
||||
self.current_char = *self.text.get(self.index)?;
|
||||
|
||||
Some(self.current_char)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Peeks the next available char
|
||||
#[inline]
|
||||
pub fn peek_char(&mut self) -> Option<char> {
|
||||
Some(*self.text.get(self.index + 1)?)
|
||||
}
|
||||
|
||||
/// Rewinds to a given index
|
||||
#[inline]
|
||||
pub fn rewind(&mut self, index: usize) {
|
||||
if self.text.len() > index {
|
||||
self.index = index;
|
||||
self.current_char = *self.text.get(index).unwrap();
|
||||
if self.index > 0 {
|
||||
self.previous_char = *self.text.get(index - 1).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rewinds to a given index and returns an error
|
||||
#[inline]
|
||||
pub fn rewind_with_error(&mut self, index: usize) -> TapeError {
|
||||
self.rewind(index);
|
||||
TapeError::new(index)
|
||||
}
|
||||
|
||||
/// Seeks one character or returns an error
|
||||
/// if there is no next character
|
||||
#[inline]
|
||||
pub fn seek_one(&mut self) -> TapeResult<()> {
|
||||
if let Some(_) = self.next_char() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TapeError::new(self.index))
|
||||
}
|
||||
}
|
||||
|
||||
/// Seeks one character and returns
|
||||
/// if it seeked or an error occurred
|
||||
#[inline]
|
||||
pub fn try_seek(&mut self) -> bool {
|
||||
self.seek_one().is_ok()
|
||||
}
|
||||
|
||||
/// Seeks any character of the given group until none is encountered anymore
|
||||
pub fn seek_any(&mut self, chars: &[char]) -> TapeResult<()> {
|
||||
while self.check_any(chars) {
|
||||
self.seek_one()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Seeks until it encounters a non whitespace character
|
||||
pub fn seek_whitespace(&mut self) {
|
||||
if self.current_char.is_whitespace() {
|
||||
while let Some(next) = self.next_char() {
|
||||
if !next.is_whitespace() || self.check_escaped() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the machine has reached the eof
|
||||
pub fn check_eof(&self) -> bool {
|
||||
self.index >= self.text.len()
|
||||
}
|
||||
|
||||
/// checks if the current char is escaped
|
||||
#[inline]
|
||||
pub fn check_escaped(&mut self) -> bool {
|
||||
let start = self.index;
|
||||
|
||||
let escaped = if self.previous_char == ESCAPE {
|
||||
self.rewind(start - 1);
|
||||
!self.check_escaped()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
self.rewind(start);
|
||||
|
||||
escaped
|
||||
}
|
||||
|
||||
/// Returns true if the given character is equal to the current one
|
||||
/// and the current character is not escaped
|
||||
#[inline]
|
||||
pub fn check_char(&mut self, value: &char) -> bool {
|
||||
self.current_char == *value && !self.check_escaped()
|
||||
}
|
||||
|
||||
/// Checks if one of the given chars matches the current one
|
||||
#[inline]
|
||||
pub fn check_any(&mut self, chars: &[char]) -> bool {
|
||||
!self.check_escaped() && chars.contains(&self.current_char)
|
||||
}
|
||||
|
||||
/// checks if the next characters match a given sequence of characters
|
||||
pub fn check_sequence(&mut self, sequence: &[char]) -> bool {
|
||||
let start_index = self.index;
|
||||
|
||||
if self.check_escaped() {
|
||||
self.rewind(start_index);
|
||||
|
||||
false
|
||||
} else {
|
||||
for sq_character in sequence {
|
||||
if self.current_char != *sq_character {
|
||||
self.rewind(start_index);
|
||||
return false;
|
||||
}
|
||||
if self.next_char() == None {
|
||||
self.rewind(start_index);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if self.index > 0 {
|
||||
self.rewind(self.index - 1);
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// checks if the next characters mach a string sequence
|
||||
pub fn check_str_sequence(&mut self, sequence: &str) -> bool {
|
||||
let start_index = self.index;
|
||||
|
||||
if self.check_escaped() {
|
||||
self.rewind(start_index);
|
||||
|
||||
false
|
||||
} else {
|
||||
let matches = sequence.chars().all(|sq_character| {
|
||||
if self.current_char != sq_character {
|
||||
self.rewind(start_index);
|
||||
return false;
|
||||
}
|
||||
if self.next_char() == None {
|
||||
self.rewind(start_index);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
});
|
||||
if !matches {
|
||||
false
|
||||
} else {
|
||||
if self.index > 0 {
|
||||
self.rewind(self.index - 1);
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// checks if the next characters match any given sequence
|
||||
#[inline]
|
||||
pub fn check_any_sequence(&mut self, sequences: &[&[char]]) -> bool {
|
||||
for seq in sequences {
|
||||
if self.check_sequence(*seq) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// checks if the next characters match any given sequence of strings
|
||||
#[inline]
|
||||
pub fn check_any_str_sequence(&mut self, sequences: &[&str]) -> bool {
|
||||
for str_seq in sequences {
|
||||
if self.check_str_sequence(str_seq) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// returns an error on the current position and optionally rewinds
|
||||
/// if a rewind index is given
|
||||
#[inline]
|
||||
pub fn assert_error(&mut self, rewind_index: Option<usize>) -> TapeError {
|
||||
if let Some(index) = rewind_index {
|
||||
self.rewind_with_error(index)
|
||||
} else {
|
||||
TapeError::new(self.index)
|
||||
}
|
||||
}
|
||||
|
||||
/// returns an error if the given char doesn't match the current one and rewinds
|
||||
/// if a rewind index is given
|
||||
#[inline]
|
||||
pub fn assert_char(&mut self, value: &char, rewind_index: Option<usize>) -> TapeResult<()> {
|
||||
if self.check_char(value) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.assert_error(rewind_index))
|
||||
}
|
||||
}
|
||||
|
||||
/// returns an error if the current char doesn't match any of the given group
|
||||
#[inline]
|
||||
pub fn assert_any(&mut self, chars: &[char], rewind_index: Option<usize>) -> TapeResult<()> {
|
||||
if self.check_any(chars) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.assert_error(rewind_index))
|
||||
}
|
||||
}
|
||||
|
||||
/// returns an error if the next chars don't match a special sequence
|
||||
#[inline]
|
||||
pub fn assert_sequence(
|
||||
&mut self,
|
||||
sequence: &[char],
|
||||
rewind_index: Option<usize>,
|
||||
) -> TapeResult<()> {
|
||||
if self.check_sequence(sequence) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.assert_error(rewind_index))
|
||||
}
|
||||
}
|
||||
|
||||
/// returns an error if the next chars don't match a special sequence
|
||||
#[inline]
|
||||
pub fn assert_str_sequence(
|
||||
&mut self,
|
||||
sequence: &str,
|
||||
rewind_index: Option<usize>,
|
||||
) -> TapeResult<()> {
|
||||
if self.check_str_sequence(sequence) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.assert_error(rewind_index))
|
||||
}
|
||||
}
|
||||
|
||||
/// returns an error if the next chars don't match any given sequence
|
||||
pub fn assert_any_sequence(
|
||||
&mut self,
|
||||
sequences: &[&[char]],
|
||||
rewind_index: Option<usize>,
|
||||
) -> TapeResult<()> {
|
||||
if self.check_any_sequence(sequences) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.assert_error(rewind_index))
|
||||
}
|
||||
}
|
||||
|
||||
/// returns an error if the next chars don't match any given sequence
|
||||
pub fn assert_any_str_sequence(
|
||||
&mut self,
|
||||
sequences: &[&str],
|
||||
rewind_index: Option<usize>,
|
||||
) -> TapeResult<()> {
|
||||
if self.check_any_str_sequence(sequences) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.assert_error(rewind_index))
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the string until any given character is matched is matched.
|
||||
/// rewinds with error if it encounters a character form the error group
|
||||
#[inline]
|
||||
pub fn get_string_until_any(&mut self, until: &[char], err_at: &[char]) -> TapeResult<String> {
|
||||
let start_index = self.index;
|
||||
|
||||
self.get_string_until_any_or_rewind(until, err_at, start_index)
|
||||
}
|
||||
|
||||
/// Returns the string until it encounters a given sequence or rewinds with error
|
||||
/// if it encounters an err sequence
|
||||
pub fn get_string_until_sequence(
|
||||
&mut self,
|
||||
until: &[&[char]],
|
||||
err_at: &[&[char]],
|
||||
) -> Result<String, TapeError> {
|
||||
let start_index = self.index;
|
||||
let mut result = String::new();
|
||||
|
||||
if self.check_any_sequence(until) {
|
||||
return Ok(result);
|
||||
} else if self.check_any_sequence(err_at) {
|
||||
return Err(TapeError::new(self.index));
|
||||
}
|
||||
|
||||
result.push(self.current_char);
|
||||
while let Some(ch) = self.next_char() {
|
||||
if self.check_any_sequence(until) || self.check_any_sequence(err_at) {
|
||||
break;
|
||||
}
|
||||
result.push(ch);
|
||||
}
|
||||
|
||||
if self.check_any_sequence(err_at) {
|
||||
Err(self.rewind_with_error(start_index))
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the string until a special char is found
|
||||
/// or rewinds if an err_at char is found
|
||||
pub fn get_string_until_any_or_rewind(
|
||||
&mut self,
|
||||
until: &[char],
|
||||
err_at: &[char],
|
||||
rewind_index: usize,
|
||||
) -> TapeResult<String> {
|
||||
let mut result = String::new();
|
||||
|
||||
if self.check_any(until) {
|
||||
return Ok(result);
|
||||
} else if self.check_any(err_at) {
|
||||
return Err(self.rewind_with_error(rewind_index));
|
||||
}
|
||||
|
||||
result.push(self.current_char);
|
||||
while let Some(ch) = self.next_char() {
|
||||
if self.check_any(until) || self.check_any(err_at) {
|
||||
break;
|
||||
}
|
||||
result.push(ch);
|
||||
}
|
||||
|
||||
if self.check_any(err_at) {
|
||||
Err(self.rewind_with_error(rewind_index))
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
use tokio::io::{AsyncRead, AsyncSeek};
|
||||
|
||||
pub trait AsyncReadSeek: AsyncRead + AsyncSeek + Unpin {}
|
Loading…
Reference in New Issue