diff --git a/Cargo.lock b/Cargo.lock index 42dde8e..1c25e25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,27 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "bitflags" version = "1.3.2" @@ -124,6 +139,88 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.17.0" @@ -178,12 +275,31 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + [[package]] name = "rust_command_line_calculator" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "clap", + "num", + "regex", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index de3ada9..6aa94c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,5 @@ path = "src/main.rs" [dependencies] clap = { version = "4.0", features = ["derive"] } anyhow = "1.0" +num = "0.4.0" +regex = "1.7.1" diff --git a/src/expression_parser.rs b/src/expression_parser.rs index 139597f..a8c4fed 100644 --- a/src/expression_parser.rs +++ b/src/expression_parser.rs @@ -1,2 +1,155 @@ +use std::fmt; +use regex::Regex; +// In an expression like `sqrt(25)` the Task would correspond to `sqrt`. This is the enum to +// configure possible Tasks. +// None means, the Expression doesn't send it's Value to a Task Handler +#[derive(Debug)] // automatically generate Debug Formatter +pub enum Task { + None, + Sqrt, + Power, + Log(u64), +} + +// How to clone a Task, i was supprised I had to do it myself. +impl Clone for Task { + fn clone(&self) -> Self { + // This can probably be done cleaner than with a verbose match. FIXME + match self { + Task::None => Task::None, + Task::Sqrt => Task::Sqrt, + Task::Power => Task::Power, + Task::Log(base) => Task::Log(*base), // TODO add base for log + } + } +} + +impl Task { + pub fn new(task_text: &str) -> Task { + match task_text { + "none" => Task::None, + "sqrt" => Task::Sqrt, + "power"|"pow" => Task::Power, + "log"|"ln" => Task::Log(10), // TODO add base + // what to do if a bad task was given: + &_ => {eprintln!("Bad Task: {}", task_text); std::process::exit(1); }, + } + } +} +// An Expression is something that can be calculated. 20+5 is an expression. Expressions can +// contain other +// Expressions and have tasks: 20+sqrt(20+5) +// Expressions are marked down with braces and a task before those braces: +// task(Expression) +// once the Value of the Expression got calculated, the calculated value should be sent to the +// TaskHandler, if the Task of the Expression is not Task::None +pub struct Expression { + text: String, + task: Task, + complex: bool, + inner_value: f64, + outer_value: f64, + children: Vec, +} + +// Debug Formatter for Expression +impl fmt::Debug for Expression { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Expression") + .field("text", &self.text) + .field("task", &self.task) + .field("is complex?", &self.complex) + .field("inner value", &self.inner_value) + .field("outer value", &self.outer_value) + .field("children", &self.children) + .finish() + } +} + +// implement clone by ourselves, as it's not automatically done for us. +impl Clone for Expression{ + fn clone(&self) -> Self { + Expression { + text: self.text.clone(), + task: self.task.clone(), + complex: self.complex.clone(), // TODO add support for complex numbers + inner_value: self.inner_value.clone(), + outer_value: self.outer_value.clone(), + children: self.children.clone(), + } + } +} + +/* + * Main logic for the Expression struct + */ +impl Expression { + /* + * Main function for making text into Expression + * example: "12 + log_10(10 + 15) + 3" + * has a sub expression log_10(10 + 5), which has Task::Log with base 10 + */ + pub fn new(expression_text: String, task: Task) -> Expression { + + // find children + // TODO add error for unused task parameters + let re_sub_expression = Regex::new(r"\w+\(.+?\)").unwrap(); + if re_sub_expression.is_match(&expression_text) { + let mut children: Vec = Vec::new(); + for sub_expression_text in re_sub_expression.captures_iter(&expression_text) { + // if any task parameters are set ( syntax: task_para(expression) ) + if sub_expression_text[0].contains('_') { + let task_and_expr: Vec<&str> = sub_expression_text[0].split(['_', '(']).collect(); + + let task_text = task_and_expr[0].clone().to_lowercase(); + let task_param = task_and_expr[1].clone().to_string(); + let task = match task_text.as_str() { + "none" => Task::None, + "sqrt" => Task::Sqrt, + "power" => Task::Power, + "log" => {let base: u64 = task_param.parse().unwrap(); Task::Log(base)}, + // what to do if a bad task was given: + &_ => {eprintln!("Bad Task: {}", task_text); std::process::exit(1); }, + }; + let expression_inner = task_and_expr[2].clone().to_string(); + children.push(Expression::new(expression_inner, task)); + } + // if there are no parameters we need to do diffrent splitting and assume defaults + else { + let task_and_expr: Vec<&str> = sub_expression_text[0].split(['(']).collect(); + + let task_text = task_and_expr[0].clone().to_lowercase(); + let task = match task_text.as_str() { + "none" => Task::None, + "sqrt" => Task::Sqrt, + "power" => Task::Power, + "log" => Task::Log(10), + // what to do if a bad task was given: + &_ => {eprintln!("Bad Task: {}", task_text); std::process::exit(1); }, + }; + let expression_inner = task_and_expr[1].clone().to_string(); + children.push(Expression::new(expression_inner, task)); + } + } + #[cfg(debug_assertions)] + dbg!(children); + } + + let expression = Expression { + text: expression_text, + // TODO generate these from the text! + task: task, + complex: false, + inner_value: 0.0, + outer_value: 0.0, + children: Vec::new(), + }; + expression + } + + pub fn process(&self) { + println!("{}", self.text); + } +} diff --git a/src/main.rs b/src/main.rs index 0385002..fc8e00d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,36 +1,51 @@ +use clap::{Parser, Subcommand}; + mod expression_parser; mod linear_algebra; -use clap::{Parser, Subcommand}; +use expression_parser::Expression; +use expression_parser::Task; + #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Arg { +// /// Optional subcommand +// #[command(subcommand)] +// command: Option, + /// Show verbose output #[arg(short, long)] verbose: bool, - /// Show debug output - #[arg(short, long, default_value_t = false)] - debug: bool, - - #[command(subcommand)] - command: Option, - /// An expression that should be used to calculate something - expressionVector: Vec, + expressions: Vec, } -#[derive(Subcommand)] -enum Commands { - /// Assert if two expressions are equal to each other - Equal { - expressionVector: Vec, - } -} +//#[derive(Subcommand)] +//enum Commands { +// /// Assert if two expressions are equal to each other +// Equal { +// } +//} fn main() { let args = Arg::parse(); - dbg!(args.expressionVector); -} + let mut expression_vec: Vec = Vec::new(); + // join expression_texts to a big expression text, split at '%'. Remove unnessecary whitespace + // at the start ot end. + // TODO implement splitting of expressions, currently they are made only into a single big + // expression text + let mut expression_texts_concat: Vec = Vec::new(); + expression_texts_concat.push(args.expressions.join(" ").trim().to_string()); + + for expression_text in expression_texts_concat { + expression_vec.push(Expression::new(expression_text, Task::None)); + } + + for expression in expression_vec { + expression.process(); + } + +}