diff --git a/.gitea/workflows/cargo.yaml b/.gitea/workflows/cargo.yaml index 67d518c..65ce966 100644 --- a/.gitea/workflows/cargo.yaml +++ b/.gitea/workflows/cargo.yaml @@ -1,9 +1,9 @@ -name: Cargo Format, Check and Test +name: Cargo Check, Format, Fix and Test on: [push, pull_request] jobs: format: - name: cargo fmt + name: cargo CI permissions: # Give the default GITHUB_TOKEN write permission to commit and push the # added or changed files to the repository. @@ -11,22 +11,17 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + - run: cargo check --all-features --all-targets - run: rustup component add rustfmt - - run: cargo fmt + - run: cargo fix --all-features --all-targets + - run: cargo fmt --all + - run: cargo test --all-features --all-targets - uses: stefanzweifel/git-auto-commit-action@v5 with: # Optional. Commit message for the created commit. # Defaults to "Apply automatic changes" - commit_message: Automatical formatting - check: - name: cargo check - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - run: cargo check --all-features --verbose - test: - name: cargo test - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - run: cargo test --all-features --verbose + commit_message: automatic cargo CI changes + commit_user_name: Gitea CI + commit_user_email: noreply@cscherr.de + commit_author: Gitea CI + diff --git a/members/libpt-ccc/src/base.rs b/members/libpt-ccc/src/base.rs deleted file mode 100644 index 0ac39bf..0000000 --- a/members/libpt-ccc/src/base.rs +++ /dev/null @@ -1,225 +0,0 @@ -//! # Results and Errors for the calculate module -//! -//! This module defines the errors and results that can be processed from any given term. - -//// ATTRIBUTES //////////////////////////////////////////////////////////////////////////////////// -// we want docs -#![warn(missing_docs)] -#![warn(rustdoc::missing_crate_level_docs)] -// we want Debug everywhere. -#![warn(missing_debug_implementations)] -// enable clippy's extra lints, the pedantic version -#![warn(clippy::pedantic)] - -//// IMPORTS /////////////////////////////////////////////////////////////////////////////////////// -use std::fmt::Display; -pub use num_traits::PrimInt; - -#[allow(unused_imports)] // we possibly want to use all log levels -use libpt_log::*; -#[allow(unused_imports)] // import more complex math stuff from there -use libpt_math; - -//// TYPES ///////////////////////////////////////////////////////////////////////////////////////// -/// Quick Result with a ccc error -pub type Result = std::result::Result; - -//// CONSTANTS ///////////////////////////////////////////////////////////////////////////////////// - -//// STATICS /////////////////////////////////////////////////////////////////////////////////////// - -//// MACROS //////////////////////////////////////////////////////////////////////////////////////// - -//// ENUMS ///////////////////////////////////////////////////////////////////////////////////////// -/// ## Supported Operations -/// -/// This `enum` contains all operations supported in this module. -#[non_exhaustive] -#[derive(Debug)] -pub enum Operator { - /// Mathmatical addition - Addition, - /// Mathmatical subtraction - Subtraction, - /// Mathmatical multiplication - Multiplication, - /// Mathmatical division - Division, - /// Mathmatical modulo, finite field arithmetic - Modulo, - /// Any function, seel [`Function`] - Function(Function) -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -/// ## Supported Functions -/// -/// This `enum` contains all functions supported in this module. -/// -/// A function has a name followed by braces directly afterwards. -/// A function may have 0 to 31 Arguments. -/// -/// Example: `sqrt(19)`, `floor(19.9)` -#[non_exhaustive] -#[derive(Debug)] -pub enum Function { - /// Draw the mathmatical root, attribute n is the nth root - Root(u16), - /// round up - Floor, - /// round down - Ceil, - /// round to nearest integer - /// (commercial rounding) - Round, -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -/// Top Level Error Type -/// -/// Contains many variants of other errors, that can occur when using the crate. -#[non_exhaustive] -#[derive(Debug)] -pub enum Error { - /// The term has bad syntax - SyntaxError(String) -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -/// Represents some kind of computed value -#[derive(Debug)] -pub enum Value { - /// Variable value - Variable(VarVal), - /// Numerical value - Numerical(NumVal), - /// Complex number value - Complex(ComplVal), -} - -/// Represents some kind of numeric value -#[non_exhaustive] -#[derive(Debug)] -pub enum NumVal { - /// Value > 0 - Signed(i128), - /// Value can be negative - Unsigned(u128), - /// Value is not an integer - Float(f64) -} - -//// STRUCTS /////////////////////////////////////////////////////////////////////////////////////// -/// Represents a Value with at least one variable, -/// -/// currently not implemented -#[derive(Debug)] -pub struct VarVal { -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -/// Represents a Value with a complex number, -/// -/// currently not implemented -#[derive(Debug)] -pub struct ComplVal { -} - -//// IMPLEMENTATION //////////////////////////////////////////////////////////////////////////////// -impl From for NumVal where - u128: TryFrom, - u128: TryFrom { - fn from(value: T) -> Self { - if T::min_value().is_zero() { - // unsigned data types - // `u128` is the largest unsigned datatype, any other type will fit. - NumVal::Unsigned(value.to_u128().unwrap()) - } - else { - // signed data types - // `i128` is the largest unsigned datatype, any other type will fit. - NumVal::Signed(value.to_i128().unwrap()) - } - } -} -//////////////////////////////////////////////////////////////////////////////////////////////////// -/// 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 - u128: TryFrom, - u128: TryFrom { - fn from(value: T) -> Self { - NumVal::from(value).into() - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -impl From for Value { - fn from(value: NumVal) -> Self { - Value::Numerical(value) - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -impl Display for Value { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Value::Numerical(val) => { - write!(f, "{}", val) - } - Value::Complex(val) => { - write!(f, "{}", val) - } - Value::Variable(val) => { - write!(f, "{}", val) - } - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -impl Display for NumVal { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - NumVal::Float(val) => { - write!(f, "{val}") - } - NumVal::Signed(val) => { - write!(f, "{val}") - } - NumVal::Unsigned(val) => { - write!(f, "{val}") - } - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -impl Display for ComplVal { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "") - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -impl Display for VarVal { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "") - } -} - -//// PUBLIC FUNCTIONS ////////////////////////////////////////////////////////////////////////////// - -//// PRIVATE FUNCTIONS ///////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/members/libpt-ccc/src/lib.rs b/members/libpt-ccc/src/lib.rs index 9eae498..3b538a6 100644 --- a/members/libpt-ccc/src/lib.rs +++ b/members/libpt-ccc/src/lib.rs @@ -19,14 +19,7 @@ #![warn(clippy::pedantic)] //// IMPORTS /////////////////////////////////////////////////////////////////////////////////////// -pub mod base; -pub use base::{Error, Result, Value}; -pub mod term; -pub use term::*; - -#[allow(unused_imports)] // we possibly want to use all log levels -use libpt_log::*; -#[allow(unused_imports)] // import more complex math stuff from there +use libpt_log; use libpt_math; //// TYPES ///////////////////////////////////////////////////////////////////////////////////////// @@ -40,44 +33,8 @@ use libpt_math; //// ENUMS ///////////////////////////////////////////////////////////////////////////////////////// //// STRUCTS /////////////////////////////////////////////////////////////////////////////////////// -/// ## A Calculator struct -/// -/// 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 //////////////////////////////////////////////////////////////////////////////// -impl Calculator { - /// Do a single calculation without doing anything else - pub fn oneshot(t: String) -> Result { - trace!(orig=t, "parsing original string to Term"); - let t = Term::new(t)?; - trace!("term has been parsed, starting Calculation"); - debug!("parsed term: {t:#?}"); - Self::calc(t) - } - - /// ## Calculate a [`Term`] - /// - /// This method makes use of the - /// [shunting yard algorithm](https://en.wikipedia.org/wiki/Shunting_yard_algorithm) to - /// Calculate the final value of any term. - /// - /// This method only processes a single term at a time, without caching. - pub fn calc(mut t: Term) -> Result { - trace!("Calculating term {t:?}"); - t.prepare()?; - t.process()?; - if t.result.is_none() { - let reason = format!("Term was processed but no result was assigned."); - // FIXME: unfitting error type - return Err(Error::SyntaxError(reason)) - } - return t.result.unwrap() - } -} //// PUBLIC FUNCTIONS ////////////////////////////////////////////////////////////////////////////// diff --git a/members/libpt-ccc/src/term.rs b/members/libpt-ccc/src/term.rs deleted file mode 100644 index f872fe5..0000000 --- a/members/libpt-ccc/src/term.rs +++ /dev/null @@ -1,223 +0,0 @@ -//! # A term that can be the input for calculation -//! -//! Short description -//! -//! Details -//! -//! ## Section 1 -//! -//! ## Section 2 - -//// ATTRIBUTES //////////////////////////////////////////////////////////////////////////////////// -// we want docs -#![warn(missing_docs)] -#![warn(rustdoc::missing_crate_level_docs)] -// we want Debug everywhere. -#![warn(missing_debug_implementations)] -// enable clippy's extra lints, the pedantic version -#![warn(clippy::pedantic)] - -use std::collections::VecDeque; - -//// IMPORTS /////////////////////////////////////////////////////////////////////////////////////// -pub use super::{Error, Result, Value, base::{self, *}}; -#[allow(unused_imports)] // we possibly want to use all log levels -use libpt_log::*; -#[allow(unused_imports)] // import more complex math stuff from there -use libpt_math; - -//// TYPES ///////////////////////////////////////////////////////////////////////////////////////// - -//// CONSTANTS ///////////////////////////////////////////////////////////////////////////////////// - -//// STATICS /////////////////////////////////////////////////////////////////////////////////////// - -//// MACROS //////////////////////////////////////////////////////////////////////////////////////// - -//// ENUMS ///////////////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////////////////////////// -/// ## Parsed value to be calculated -/// -/// This enum represents anything that goes to the output queue of [`Term::prepare()`] and will -/// then be used to actually calculate something in [`Term::process()`]. -#[derive(Debug)] -enum Token { - /// Some kind of operator - #[allow(unused)] // tmp - Operator(Operator), - /// A concrete value that we can calculate something with. May be a constant, integer, float, - /// etc. - /// The Token has a value that can be used in calculation - Value(super::base::Value), - /// A variable of some kind that will be present in the result - #[allow(unused)] // tmp - Variable(char), -} -//// STRUCTS /////////////////////////////////////////////////////////////////////////////////////// -/// ## Term that can be calculated -/// -/// Represents a signular term, that can be calculated. Terms will be evaluated by the [`Term::prepare`] -/// function, afterwards calculated (as much as possible) in the [`Term::process`] function. -/// -#[derive(Debug)] -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>, - ///////////////////////////////////// - ///// internal values following ///// - ///////////////////////////////////// - #[allow(unused)] // tmp - operator_stack: Vec, - output_queue: VecDeque -} - -//// IMPLEMENTATION //////////////////////////////////////////////////////////////////////////////// -impl Term { - /// Build a new term from an expression - /// - /// Invalid terms will result in an [`Err`]. - pub fn new(orig: String) -> Result { - return Ok( - Term { - original: orig, - text: String::new(), // will be initialized in `prepare()` - result: None, - operator_stack: Vec::new(), - output_queue: VecDeque::new() - } - ) - } - - /// Prepare the term for the processing. - pub fn prepare(&mut self) -> Result<()> { - trace!("preparing term: {:#?}", self); - self.text = Self::filter(&self.original)?; - - // Storage for unfinished tokens - let _unfinished_chars: Vec = Vec::new(); - - for (_index, c) in self.original.chars().enumerate() { - // 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 - _ => { - let reason = format!("The meaning of '{c}' could not be identified."); - warn!(reason); - return Err(Error::SyntaxError(reason)); - } - } - } - Ok(()) - } - - /// Process a prepared term, calculating it's result - pub fn process(&mut self) -> Result<()> { - debug!("processing term: {:#?}", self); - debug!("queue: {:#?}", self.output_queue); - // TODO: process RPN and set result - self.result = Some(Ok(19.into())); - Ok(()) - } - - /// Convert a character into a token - /// - /// Returns: A tuple with a [`Token`] and a [`bool`]. If the bool is false, the [`Token`] is - /// marked as "incomplete", meaning that the character cannot be used yet. - #[allow(unused)] // tmp - fn to_tok(_s: Vec) -> Result { - Ok(19.into()) - } - - /// only leave relevant chars for calculation - // 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 Ok(filtered) - } - - /// check if we should ignore this character - fn is_ignore(c: &char) -> bool { - match *c { - ' ' => true, - _ => false - } - } - - /// allowed special chars - fn is_allowed_special_c(c: &char) -> bool { - match *c { - '+' | '-' | '*' | '/' | '%' => true, - _ => false - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -/// Helper methods for Tokens -impl Token { } - -//////////////////////////////////////////////////////////////////////////////////////////////////// -impl From for Token where - T: Into, - T: PrimInt, - u128: TryFrom - { - fn from(value: T) -> Self { - Token::Value(base::Value::from(value)) - } -} - -//// PUBLIC FUNCTIONS ////////////////////////////////////////////////////////////////////////////// - -//// PRIVATE FUNCTIONS ///////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/members/libpt-core/Cargo.toml b/members/libpt-core/Cargo.toml index 7ae9aaf..769f494 100644 --- a/members/libpt-core/Cargo.toml +++ b/members/libpt-core/Cargo.toml @@ -13,3 +13,6 @@ keywords.workspace = true categories.workspace = true [dependencies] +anyhow = "1.0.79" +cucumber = "0.20.2" +libpt-log = {path = "../libpt-log" }