commit d1cd958e869aaf781360dc57ef21a6ac1d948604 Author: trivernis Date: Tue Jul 28 19:57:14 2020 +0200 Add initial functions for the CharTapeMachine diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..408b8a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ee298ba --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "charred" +version = "0.1.0" +authors = ["trivernis "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0478c45 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,5 @@ +mod tapemachine; + +#[cfg(test)] +mod tests { +} diff --git a/src/tapemachine.rs b/src/tapemachine.rs new file mode 100644 index 0000000..3c2eaad --- /dev/null +++ b/src/tapemachine.rs @@ -0,0 +1,276 @@ +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 + } + } +} + +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 = Result; + +const ESCAPE: char = '\\'; + +pub struct CharTapeMachine { + index: usize, + text: Vec, + current_char: char, + previous_char: char, +} + +impl CharTapeMachine { + pub fn new(text: Vec) -> Self { + let current_char = if text.len() > 0 { + *text.first().unwrap() + } else { + ' ' + }; + Self { + text, + index: 0, + previous_char: current_char, + current_char, + } + } + + /// Returns the next char + /// if there is any + pub fn next_char(&mut self) -> Option { + 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 { + Some(*self.text.get(self.index + 1)?) + } + + /// Rewinds to a given index + #[inline] + pub fn rewind(&mut self, index: usize) { + self.index = index; + self.current_char = *self.text.get(index).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 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 current char is escaped + #[inline] + pub fn check_escaped(&mut self) -> bool { + self.previous_char == ESCAPE + } + + /// 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 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 + } + + /// returns an error on the current position and optionally rewinds + /// if a rewind index is given + #[inline] + fn assert_error(&mut self, rewind_index: Option) -> 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) -> 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) -> 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) -> TapeResult<()> { + if self.check_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) -> TapeResult<()> { + if self.check_any_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 + pub fn get_string_until_any( + &mut self, + until: &[char], + err_at: &[char], + ) -> TapeResult { + let start_index = self.index; + let mut result = String::new(); + + if self.check_any(until) { + return Ok(result); + } else if self.check_any(err_at) { + return Err(TapeError::new(self.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(start_index)) + } else { + Ok(result) + } + } + + /// Returns the string until it encounters a given sequence or rewinds with error + /// if it encounters an err sequence + fn get_string_until_sequence( + &mut self, + until: &[&[char]], + err_at: &[&[char]], + ) -> Result { + 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) + } + } +} \ No newline at end of file