From f8f88e79c301fc0182704116f8066c6913de8d57 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 12 Sep 2023 22:49:40 +0200 Subject: [PATCH] basic filter and error display --- src/bin/ccc/mod.rs | 9 ++++- src/math/calculator/base.rs | 13 ++++++- src/math/calculator/mod.rs | 6 ++-- src/math/calculator/term.rs | 69 +++++++++++++++++++++++++++++++------ 4 files changed, 83 insertions(+), 14 deletions(-) diff --git a/src/bin/ccc/mod.rs b/src/bin/ccc/mod.rs index b54a719..2edfdf2 100644 --- a/src/bin/ccc/mod.rs +++ b/src/bin/ccc/mod.rs @@ -129,6 +129,13 @@ fn main() { debug!("exporssion: {}", expr); let r = Calculator::oneshot(expr); - println!("{}", r.unwrap()); + match r { + Ok(r) => { + println!("{r}"); + } + Err(err) => { + error!("Could not compute: {err}"); + } + } } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/math/calculator/base.rs b/src/math/calculator/base.rs index 54d7a80..3ff2b71 100644 --- a/src/math/calculator/base.rs +++ b/src/math/calculator/base.rs @@ -72,7 +72,7 @@ pub enum Function { #[non_exhaustive] #[derive(Debug)] pub enum Error { - SyntaxError + SyntaxError(String) } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -120,6 +120,17 @@ impl From for NumVal where } } } +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// Display Errors with a nice little reason +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SyntaxError(reason) => { + write!(f, "Syntax Error: {}", reason) + } + } + } +} //////////////////////////////////////////////////////////////////////////////////////////////////// impl From for Value where diff --git a/src/math/calculator/mod.rs b/src/math/calculator/mod.rs index f99e7b1..c44f891 100644 --- a/src/math/calculator/mod.rs +++ b/src/math/calculator/mod.rs @@ -40,6 +40,7 @@ use crate::logger::{trace, debug, info, warn, error}; /// This struct does not do anything at the moment, but aims to be the target for high level /// control. Instead of using the [`Calculator`], you could just use the [`Term`] struct for /// lower level control. +#[derive(Debug)] pub struct Calculator; //// IMPLEMENTATION //////////////////////////////////////////////////////////////////////////////// @@ -65,8 +66,9 @@ impl Calculator { t.prepare()?; t.process()?; if t.result.is_none() { - error!("Term was processed but no result was assigned."); - return Err(Error::SyntaxError) + let reason = format!("Term was processed but no result was assigned."); + // FIXME: unfitting error type + return Err(Error::SyntaxError(reason)) } return t.result.unwrap() } diff --git a/src/math/calculator/term.rs b/src/math/calculator/term.rs index 3bd6445..c40cf87 100644 --- a/src/math/calculator/term.rs +++ b/src/math/calculator/term.rs @@ -58,6 +58,8 @@ enum Token { pub struct Term { /// the original expression to calculate pub original: String, + /// the filtered text of the expression, only with relevant information + pub text: String, /// the calculated result, may be of diffrent types, see [`crate::math::calculator::result`]. pub result: Option>, ///////////////////////////////////// @@ -76,6 +78,7 @@ impl Term { return Ok( Term { original: orig, + text: String::new(), // will be initialized in `prepare()` result: None, operator_stack: Vec::new(), output_queue: VecDeque::new() @@ -85,22 +88,20 @@ impl Term { /// Prepare the term for the processing. pub fn prepare(&mut self) -> Result<()> { - // TODO: shunting yard + trace!("preparing term: {:#?}", self); + self.text = Self::filter(&self.original)?; // Storage for unfinished tokens let mut unfinished_chars: Vec = Vec::new(); for (index, c) in self.original.chars().enumerate() { - if !c.is_alphanumeric() { - // TODO: allow any unicode char to be a variable - warn!("'{c}' is not a valid character, only alphanumeric input is allowed."); - return Err(Error::SyntaxError); - } // this will be a mess, but it has to be before i can sort the mess. match c { + // TODO: make function to check if character is an operator, use it _ => { - warn!("The meaning of '{c}' could not be identified."); - return Err(Error::SyntaxError); + let reason = format!("The meaning of '{c}' could not be identified."); + warn!(reason); + return Err(Error::SyntaxError(reason)); } } } @@ -125,14 +126,54 @@ impl Term { } /// only leave relevant chars for calculation - fn filter(s: String) -> String { + // TODO: make function to check if character is an operator, use it + fn filter(s: &str) -> Result { + // pre checks + // NOTE: Apperently, "alphanumeric" in Rust is a pretty broad term. + // Even CJK characters or Japanese Kana are allowed: + // - 'さ' alphanumeric + // - '数' alphanumeric + // - '学' alphanumeric + // - '+' not alphanumeric + for c in s.chars() { + #[cfg(debug_assertions)] { + debug!("filter checks for '{c}': + alphanumeric: {} + allowed special: {} + EXCEPT IF + ascii control: {} + ", + !c.is_alphanumeric(), + !Self::is_allowed_special_c(&c), + c.is_ascii_control(), + ) + } + if + ( + !c.is_alphanumeric() || + !Self::is_allowed_special_c(&c) + ) + && + ( + c.is_ascii_control() + ) + { + // TODO: allow any unicode char to be a variable + let reason = format!("'{c}' is not a valid character, only alphanumeric, punctuation, operators are allowed."); + warn!(reason); + return Err(Error::SyntaxError(reason)); + } + } + + // filter out single chars let mut filtered = String::new(); for c in s.chars() { if !Self::is_ignore(&c) { filtered.push(c); } } - return filtered + + return Ok(filtered) } /// check if we should ignore this character @@ -142,6 +183,14 @@ impl Term { _ => false } } + + /// allowed special chars + fn is_allowed_special_c(c: &char) -> bool { + match *c { + '+' | '-' | '*' | '/' | '%' => true, + _ => false + } + } } ////////////////////////////////////////////////////////////////////////////////////////////////////