From 9d1a7f7b71665f2bb05c1a20262fe0c752d1f3f9 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sat, 11 Feb 2023 19:18:10 +0100 Subject: [PATCH 01/12] ignore Cargo.lock change version number --- .gitignore | 1 + Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 96a4b3b..4f9eabe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /bin/** +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 6aa94c1..39f80cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_command_line_calculator" -version = "0.1.1" +version = "0.1.2" edition = "2021" authors = ["Christoph J. Scherr "] license = "GPL3" From ceffa6bf3c164c972c6a6f2f5a793af47aa1feed Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sat, 11 Feb 2023 19:41:03 +0100 Subject: [PATCH 02/12] code clean up --- Cargo.lock | 452 --------------------------------------- src/expression_parser.rs | 62 +++--- src/main.rs | 2 +- 3 files changed, 31 insertions(+), 485 deletions(-) delete mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 1c25e25..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,452 +0,0 @@ -# This file is automatically @generated by Cargo. -# 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] -name = "clap" -version = "4.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" -dependencies = [ - "bitflags", - "clap_derive", - "clap_lex", - "is-terminal", - "once_cell", - "strsim", - "termcolor", -] - -[[package]] -name = "clap_derive" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - -[[package]] -name = "io-lifetimes" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "is-terminal" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys", -] - -[[package]] -name = "libc" -version = "0.2.139" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" - -[[package]] -name = "linux-raw-sys" -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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" - -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" -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.1" -dependencies = [ - "anyhow", - "clap", - "num", - "regex", -] - -[[package]] -name = "rustix" -version = "0.36.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "unicode-ident" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/src/expression_parser.rs b/src/expression_parser.rs index a8c4fed..3bdb789 100644 --- a/src/expression_parser.rs +++ b/src/expression_parser.rs @@ -26,8 +26,9 @@ impl Clone for Task { } impl Task { - pub fn new(task_text: &str) -> Task { - match task_text { + pub fn new(task_text: &str, task_param: &str) -> Task { + let task_text = task_text.to_lowercase(); + match task_text.as_str() { "none" => Task::None, "sqrt" => Task::Sqrt, "power"|"pow" => Task::Power, @@ -94,46 +95,41 @@ impl Expression { // find children // TODO add error for unused task parameters - let re_sub_expression = Regex::new(r"\w+\(.+?\)").unwrap(); + // TODO add supprot for truly recursie expressions, currently only one expression can be in + // a root expression. + let re_sub_expression = Regex::new(r"\w+\(.+?\)").unwrap(); // FIXME doesnt support nested + // expressions!!! 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)); + let task_and_expr: Vec<&str> = sub_expression_text[0].split(['_', '(']).collect(); + #[cfg(debug_assertions)] + dbg!(&task_and_expr); + let task = Task::new(task_and_expr[0], task_and_expr[1]); + let mut expression_inner = task_and_expr[2].clone().to_string(); + #[cfg(debug_assertions)] + dbg!(&expression_inner); + expression_inner.pop(); + #[cfg(debug_assertions)] + dbg!(&expression_inner); + 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)); + let task_and_expr: Vec<&str> = sub_expression_text[0].split(['(']).collect(); + #[cfg(debug_assertions)] + dbg!(&task_and_expr); + let task_text = task_and_expr[0].clone().to_lowercase(); + let task = Task::new(&task_text, ""); + let mut expression_inner = task_and_expr[1].clone().to_string(); + expression_inner.pop(); + #[cfg(debug_assertions)] + dbg!(&expression_inner); + children.push(Expression::new(expression_inner, task)); } } - #[cfg(debug_assertions)] - dbg!(children); } let expression = Expression { @@ -145,6 +141,8 @@ impl Expression { outer_value: 0.0, children: Vec::new(), }; + #[cfg(debug_assertions)] + dbg!(&expression); expression } diff --git a/src/main.rs b/src/main.rs index fc8e00d..1c5fe2a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use clap::{Parser, Subcommand}; +use clap::Parser; mod expression_parser; mod linear_algebra; From 58e6f57f773d2e08e8460e9c60e05115cfc78c35 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sun, 12 Feb 2023 03:50:33 +0100 Subject: [PATCH 03/12] brace grouping finally works --- .gitignore | 1 + .swp | Bin 0 -> 12288 bytes .vscode/launch.json | 45 +++++++++++++++ src/expression_parser.rs | 118 +++++++++++++++++++++++++++------------ test.txt | 1 + 5 files changed, 129 insertions(+), 36 deletions(-) create mode 100644 .swp create mode 100644 .vscode/launch.json create mode 100644 test.txt diff --git a/.gitignore b/.gitignore index 4f9eabe..38461ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target /bin/** Cargo.lock +Cargo.lock diff --git a/.swp b/.swp new file mode 100644 index 0000000000000000000000000000000000000000..06b03579f37cfb19f68c52844e9c739e65c11a7c GIT binary patch literal 12288 zcmeI&u@1pd6vpvWi1O$>%6t;8VlBwoN{8Q5$r7Du&Tjc_j#ucA%Sm(_F`CDA%5o5D2&5cs-4Z4wMz z_NaF|ZPRK_zP=+ifB*srAbh#nb=yI(wV@Glp>f8Fo?9(qo&E*v`@Nn#01B!ao&W#< literal 0 HcmV?d00001 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6dcd831 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Verwendet IntelliSense zum Ermitteln möglicher Attribute. + // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen. + // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'rclc'", + "cargo": { + "args": [ + "run", + "--bin=rclc", + "--package=rust_command_line_calculator", + ], + "filter": { + "name": "rclc", + "kind": "bin" + } + }, + "args": ["(())()"], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'rclc'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=rclc", + "--package=rust_command_line_calculator", + ], + "filter": { + "name": "rclc", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/src/expression_parser.rs b/src/expression_parser.rs index 3bdb789..0fc4a2f 100644 --- a/src/expression_parser.rs +++ b/src/expression_parser.rs @@ -1,4 +1,6 @@ use std::fmt; +use std::collections::HashMap; +use std::hash::Hash; use regex::Regex; // In an expression like `sqrt(25)` the Task would correspond to `sqrt`. This is the enum to @@ -82,6 +84,59 @@ impl Clone for Expression{ } } +fn find_brace_groups(haystack: String) -> Vec> { + + // TODO add support for diffrent braces + // TODO add error if not all braces are closed + let mut parenthesis_group: Vec<(usize, usize)> = Vec::new(); + let mut parenthesis_open: usize = 0; + let mut parenthesis_open_processed: usize = 0; + let mut parenthesis_closed_processed: usize = 0; + let mut parenthesis_last_opened: Vec = Vec::new(); + //let mut brackets_group: Vec<(usize, usize)> = Vec::new(); + //let mut brackets_open: usize = 0; + //let mut square_braces_group: Vec<(usize, usize)> = Vec::new(); + //let mut square_braces_open: usize = 0; + // first open stuff + for (index, char) in haystack.chars().enumerate() { + match char { + '(' => { + #[cfg(debug_assertions)] + { + dbg!(char); + dbg!(index); + } + parenthesis_group.push((index, 0)); + parenthesis_open = parenthesis_open + 1; + parenthesis_last_opened.push(parenthesis_open_processed); + parenthesis_open_processed = parenthesis_open_processed + 1; + }, + ')' => { + let len = parenthesis_group.len(); + #[cfg(debug_assertions)] + { + dbg!(char); + dbg!(index); + dbg!(parenthesis_last_opened.len()); + dbg!(parenthesis_last_opened[parenthesis_last_opened.len() - 1]); + } + parenthesis_group[parenthesis_last_opened[parenthesis_last_opened.len() - 1]].1 = index; + parenthesis_open = parenthesis_open - 1; + parenthesis_closed_processed = parenthesis_closed_processed + 1; + parenthesis_last_opened.pop(); + // TODO add error if no parenthesis is open yet. + }, + _ => (), + } + } + // now iterate backwards and search for closing things + + let brace_groups = vec![parenthesis_group/*, square_braces_group, brackets_group*/]; + #[cfg(debug_assertions)] + dbg!(&brace_groups); + return brace_groups; +} + /* * Main logic for the Expression struct */ @@ -93,48 +148,25 @@ impl Expression { */ pub fn new(expression_text: String, task: Task) -> Expression { - // find children - // TODO add error for unused task parameters - // TODO add supprot for truly recursie expressions, currently only one expression can be in - // a root expression. - let re_sub_expression = Regex::new(r"\w+\(.+?\)").unwrap(); // FIXME doesnt support nested - // expressions!!! - if re_sub_expression.is_match(&expression_text) { + let re_contains_sub_expression= Regex::new(r"(\(.*\))|(\[.*\])|(\{.*\})").unwrap(); + if re_contains_sub_expression.is_match(expression_text.as_str()) { + let brace_groups: Vec> = find_brace_groups(expression_text.clone()); + + let mut brace_groups_texts: Vec = Vec::new(); 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(); - #[cfg(debug_assertions)] - dbg!(&task_and_expr); - let task = Task::new(task_and_expr[0], task_and_expr[1]); - let mut expression_inner = task_and_expr[2].clone().to_string(); - #[cfg(debug_assertions)] - dbg!(&expression_inner); - expression_inner.pop(); - #[cfg(debug_assertions)] - dbg!(&expression_inner); - 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(); - #[cfg(debug_assertions)] - dbg!(&task_and_expr); - let task_text = task_and_expr[0].clone().to_lowercase(); - let task = Task::new(&task_text, ""); - let mut expression_inner = task_and_expr[1].clone().to_string(); - expression_inner.pop(); - #[cfg(debug_assertions)] - dbg!(&expression_inner); - children.push(Expression::new(expression_inner, task)); + + for brace_group in brace_groups { + for pair in brace_group { + let text = &expression_text[pair.0..pair.1 + 1]; + let text = &text[1..text.len() - 1]; + dbg!(text); } } - } + } + let expression = Expression { text: expression_text, - // TODO generate these from the text! task: task, complex: false, inner_value: 0.0, @@ -151,3 +183,17 @@ impl Expression { } } +enum Brace { + Open(char), + Closed(char), +} + +impl Brace { + pub fn new(c: char) -> Option { + match c { + '{' | '[' | '(' => Some(Brace::Open(c)), + '}' | ']' | ')' => Some(Brace::Closed(c)), + _ => None, + } + } +} \ No newline at end of file diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..ae08274 --- /dev/null +++ b/test.txt @@ -0,0 +1 @@ +13 + 2525 + sqrt(15 + log_10(100)) + power_10(10) From f03541d056ca149848e1d11b7e12d69dfff699d9 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sun, 12 Feb 2023 15:24:59 +0100 Subject: [PATCH 04/12] potential task formulation --- src/expression_parser.rs | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/expression_parser.rs b/src/expression_parser.rs index 0fc4a2f..8e6958a 100644 --- a/src/expression_parser.rs +++ b/src/expression_parser.rs @@ -155,11 +155,19 @@ impl Expression { let mut brace_groups_texts: Vec = Vec::new(); let mut children: Vec = Vec::new(); + // 1 brace group per possible combination, by default, this is only (), so 1 iteration. + // This is still O(n¹) for brace_group in brace_groups { for pair in brace_group { let text = &expression_text[pair.0..pair.1 + 1]; let text = &text[1..text.len() - 1]; + #[cfg(debug_assertions)] dbg!(text); + brace_groups_texts.push(text.to_string()); + // we have the expression_text, now we just need to get the task until we can + // pass these parameters into Expression::new(). This is the recursive part. + let possible_task = &expression_text[..pair.0].chars().rev().collect::(); + dbg!(possible_task); } } } @@ -182,18 +190,3 @@ impl Expression { println!("{}", self.text); } } - -enum Brace { - Open(char), - Closed(char), -} - -impl Brace { - pub fn new(c: char) -> Option { - match c { - '{' | '[' | '(' => Some(Brace::Open(c)), - '}' | ']' | ')' => Some(Brace::Closed(c)), - _ => None, - } - } -} \ No newline at end of file From d5ee7a420a9f614430590e69ae97702792f8b5ed Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sun, 12 Feb 2023 15:46:09 +0100 Subject: [PATCH 05/12] task param parsing implementation --- src/expression_parser.rs | 83 +++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/src/expression_parser.rs b/src/expression_parser.rs index 8e6958a..cdeed2d 100644 --- a/src/expression_parser.rs +++ b/src/expression_parser.rs @@ -1,17 +1,16 @@ use std::fmt; -use std::collections::HashMap; -use std::hash::Hash; 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), + Root(u64), + Power(f64), + Log(f64), } // How to clone a Task, i was supprised I had to do it myself. @@ -20,8 +19,8 @@ impl Clone for Task { // 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::Root(depth) => Task::Root(*depth), + Task::Power(exp) => Task::Power(*exp), Task::Log(base) => Task::Log(*base), // TODO add base for log } } @@ -29,12 +28,51 @@ impl Clone for Task { impl Task { pub fn new(task_text: &str, task_param: &str) -> Task { + if task_text.is_empty() { + return Task::None; + } let task_text = task_text.to_lowercase(); match task_text.as_str() { "none" => Task::None, - "sqrt" => Task::Sqrt, - "power"|"pow" => Task::Power, - "log"|"ln" => Task::Log(10), // TODO add base + "sqrt"|"root" => { + if task_param.is_empty() { + return Task::Root(2); + } + let pot_param = task_param.parse::(); + match pot_param { + Ok(value) => {Task::Root(value)}, + Err(error) => { + eprintln!("could not parse task parameter: {error}"); + std::process::exit(1); + }, + } + }, + "power"|"pow"|"sq" => { + if task_param.is_empty() { + return Task::Power(2.0); + } + let pot_param = task_param.parse::(); + match pot_param { + Ok(value) => {Task::Power(value)}, + Err(error) => { + eprintln!("could not parse task parameter: {error}"); + std::process::exit(1); + }, + } + }, + "log"|"ln" => { + if task_param.is_empty() { + return Task::Log(10.0); + } + let pot_param = task_param.parse::(); + match pot_param { + Ok(value) => {Task::Log(value)}, + Err(error) => { + eprintln!("could not parse task parameter: {error}"); + std::process::exit(1); + }, + } + }, // what to do if a bad task was given: &_ => {eprintln!("Bad Task: {}", task_text); std::process::exit(1); }, } @@ -42,7 +80,8 @@ impl Task { } // 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 and have tasks: 20+log_10(20+5) +// Tasks may have parameters, denoted using an underscore '_' // 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 @@ -112,7 +151,6 @@ fn find_brace_groups(haystack: String) -> Vec> { parenthesis_open_processed = parenthesis_open_processed + 1; }, ')' => { - let len = parenthesis_group.len(); #[cfg(debug_assertions)] { dbg!(char); @@ -162,12 +200,29 @@ impl Expression { let text = &expression_text[pair.0..pair.1 + 1]; let text = &text[1..text.len() - 1]; #[cfg(debug_assertions)] - dbg!(text); brace_groups_texts.push(text.to_string()); // we have the expression_text, now we just need to get the task until we can // pass these parameters into Expression::new(). This is the recursive part. let possible_task = &expression_text[..pair.0].chars().rev().collect::(); - dbg!(possible_task); + let mut stop_at: usize = 0; + // TODO check for task parameters + for (index, char) in possible_task.chars().enumerate() { + stop_at = index; + if !(char.is_alphanumeric() | (char == '.') | (char == '_')) { + break; + } + } + let task_text_full = possible_task.clone()[..stop_at + 1].chars().rev().collect::(); + let task: Task; + if task_text_full.contains('_') { + let split: Vec<&str> = task_text_full.split('_').collect(); + task = Task::new(split[0], split[1]); + } + else { + task = Task::new(task_text_full.as_str(), ""); + } + let child = Expression::new(text.to_string(), task); + children.push(child); } } } From eab8d90a65481fd53044db7cc186c640d85f23e9 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sun, 12 Feb 2023 23:05:13 +0100 Subject: [PATCH 06/12] better help --- src/main.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1c5fe2a..4bace33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,11 +14,27 @@ struct Arg { // #[command(subcommand)] // command: Option, - /// Show verbose output - #[arg(short, long)] - verbose: bool, - - /// An expression that should be used to calculate something + ///Syntax: '1 + task_param(inner) + 1 '{n} + ///{n} + ///Specify an expression, any expression may contain child expressions, which can be denoted{n} + ///with parenthesis '(child)'. Expressions may have a task applied to them, such as a + ///logarithm{n} or drawing a root. To apply a task to a expression, simply write the name of{n} + ///the task before denoting an expression: 'myTask(myExpression)'. You can apply a parameter{n} + ///to some tasks by using an underscore '_': 'myTask_myParameter(myExpression)'.{n} + ///{n} + ///List of Tasks:{n} + ///{n} + ///"none" explicitly set no task for expression{n} + /// parameter: none + ///{n} + ///"root" or "sqrt" draw the root of the expression{n} + /// parameter: draw n'th root of expression, default is 2.0{n} + ///{n} + ///"power" or "pow" or "sq" apply an exponent to the expression{n} + /// parameter: specify exponent n, default is 2.0{n} + ///{n} + ///"log" or "ln" apply a logarithm to the expression{n} + /// parameter: specify base n, default is 10{n} expressions: Vec, } From 4dbd9e6afb5850b9a47288b259d29aae5dc9de6c Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sun, 12 Feb 2023 23:22:57 +0100 Subject: [PATCH 07/12] normalizing expression texts --- src/expression_parser.rs | 111 ++++++++++++++++++++++++--------------- src/main.rs | 11 ---- 2 files changed, 68 insertions(+), 54 deletions(-) diff --git a/src/expression_parser.rs b/src/expression_parser.rs index cdeed2d..4e5134b 100644 --- a/src/expression_parser.rs +++ b/src/expression_parser.rs @@ -1,7 +1,17 @@ use std::fmt; +use std::collections::HashMap; use regex::Regex; +pub const OPERATORS: [char; 5] = ['+', '-', '*', '/', '^']; + +fn normalize_string(to_normalize: String) -> String { + let mut normalized_text = to_normalize; + normalized_text.retain(|c| !c.is_whitespace()); + normalized_text = normalized_text.to_string(); + normalized_text +} + // 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 @@ -125,54 +135,54 @@ impl Clone for Expression{ fn find_brace_groups(haystack: String) -> Vec> { - // TODO add support for diffrent braces - // TODO add error if not all braces are closed - let mut parenthesis_group: Vec<(usize, usize)> = Vec::new(); - let mut parenthesis_open: usize = 0; - let mut parenthesis_open_processed: usize = 0; - let mut parenthesis_closed_processed: usize = 0; - let mut parenthesis_last_opened: Vec = Vec::new(); - //let mut brackets_group: Vec<(usize, usize)> = Vec::new(); - //let mut brackets_open: usize = 0; - //let mut square_braces_group: Vec<(usize, usize)> = Vec::new(); - //let mut square_braces_open: usize = 0; - // first open stuff - for (index, char) in haystack.chars().enumerate() { - match char { - '(' => { - #[cfg(debug_assertions)] - { + // TODO add support for diffrent braces + // TODO add error if not all braces are closed + let mut parenthesis_group: Vec<(usize, usize)> = Vec::new(); + let mut parenthesis_open: usize = 0; + let mut parenthesis_open_processed: usize = 0; + let mut parenthesis_closed_processed: usize = 0; + let mut parenthesis_last_opened: Vec = Vec::new(); + //let mut brackets_group: Vec<(usize, usize)> = Vec::new(); + //let mut brackets_open: usize = 0; + //let mut square_braces_group: Vec<(usize, usize)> = Vec::new(); + //let mut square_braces_open: usize = 0; + // first open stuff + for (index, char) in haystack.chars().enumerate() { + match char { + '(' => { + #[cfg(debug_assertions)] + { dbg!(char); dbg!(index); - } - parenthesis_group.push((index, 0)); - parenthesis_open = parenthesis_open + 1; - parenthesis_last_opened.push(parenthesis_open_processed); - parenthesis_open_processed = parenthesis_open_processed + 1; - }, - ')' => { - #[cfg(debug_assertions)] - { + } + parenthesis_group.push((index, 0)); + parenthesis_open = parenthesis_open + 1; + parenthesis_last_opened.push(parenthesis_open_processed); + parenthesis_open_processed = parenthesis_open_processed + 1; + }, + ')' => { + #[cfg(debug_assertions)] + { dbg!(char); dbg!(index); dbg!(parenthesis_last_opened.len()); dbg!(parenthesis_last_opened[parenthesis_last_opened.len() - 1]); - } - parenthesis_group[parenthesis_last_opened[parenthesis_last_opened.len() - 1]].1 = index; - parenthesis_open = parenthesis_open - 1; - parenthesis_closed_processed = parenthesis_closed_processed + 1; - parenthesis_last_opened.pop(); - // TODO add error if no parenthesis is open yet. - }, - _ => (), - } + } + parenthesis_group[parenthesis_last_opened[parenthesis_last_opened.len() - 1]].1 = index; + parenthesis_open = parenthesis_open - 1; + parenthesis_closed_processed = parenthesis_closed_processed + 1; + parenthesis_last_opened.pop(); + // TODO add error if no parenthesis is open yet. + }, + _ => (), } - // now iterate backwards and search for closing things + } + // now iterate backwards and search for closing things - let brace_groups = vec![parenthesis_group/*, square_braces_group, brackets_group*/]; - #[cfg(debug_assertions)] - dbg!(&brace_groups); - return brace_groups; + let brace_groups = vec![parenthesis_group/*, square_braces_group, brackets_group*/]; + #[cfg(debug_assertions)] + dbg!(&brace_groups); + return brace_groups; } /* @@ -186,6 +196,8 @@ impl Expression { */ pub fn new(expression_text: String, task: Task) -> Expression { + let expression_text = normalize_string(expression_text); + let re_contains_sub_expression= Regex::new(r"(\(.*\))|(\[.*\])|(\{.*\})").unwrap(); if re_contains_sub_expression.is_match(expression_text.as_str()) { let brace_groups: Vec> = find_brace_groups(expression_text.clone()); @@ -208,11 +220,11 @@ impl Expression { // TODO check for task parameters for (index, char) in possible_task.chars().enumerate() { stop_at = index; - if !(char.is_alphanumeric() | (char == '.') | (char == '_')) { + if !(char.is_alphanumeric() | (char == '.') | (char == '_')) & (char == '+') { break; } } - let task_text_full = possible_task.clone()[..stop_at + 1].chars().rev().collect::(); + let task_text_full = possible_task.clone()[..stop_at + 0].chars().rev().collect::(); let task: Task; if task_text_full.contains('_') { let split: Vec<&str> = task_text_full.split('_').collect(); @@ -241,7 +253,20 @@ impl Expression { expression } + // calculate value for expression. pub fn process(&self) { - println!("{}", self.text); + let normalized_text = self.normalize_text(); + // iterate through chars. Find logical groups, and add them into a hashmap. + let re_numeric = Regex::new(r"\d+(\.\d+)?"); + todo!(); + // no idea how to do this, seriously, this is so complicated, i wouldn't ever have thought + // this. I probably need to add a enum Operation and an enum Value to create a + // HashMap and match back the Value type to some real usable one? This is so + // complicated. + } + + // wrapper for normalize_string() + fn normalize_text(&self) -> String { + normalize_string(self.text.clone()) } } diff --git a/src/main.rs b/src/main.rs index 4bace33..c3ea9d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,10 +10,6 @@ use expression_parser::Task; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Arg { -// /// Optional subcommand -// #[command(subcommand)] -// command: Option, - ///Syntax: '1 + task_param(inner) + 1 '{n} ///{n} ///Specify an expression, any expression may contain child expressions, which can be denoted{n} @@ -38,13 +34,6 @@ struct Arg { expressions: Vec, } -//#[derive(Subcommand)] -//enum Commands { -// /// Assert if two expressions are equal to each other -// Equal { -// } -//} - fn main() { let args = Arg::parse(); let mut expression_vec: Vec = Vec::new(); From 55c3e142b74b4db52af12c18fac0b923c758be88 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 Feb 2023 22:04:46 +0100 Subject: [PATCH 08/12] expression parser logic fix --- src/expression_parser.rs | 78 +++++++++++++++++++++++++++------------- src/main.rs | 3 +- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/expression_parser.rs b/src/expression_parser.rs index 4e5134b..4389676 100644 --- a/src/expression_parser.rs +++ b/src/expression_parser.rs @@ -1,10 +1,6 @@ use std::fmt; -use std::collections::HashMap; use regex::Regex; - -pub const OPERATORS: [char; 5] = ['+', '-', '*', '/', '^']; - fn normalize_string(to_normalize: String) -> String { let mut normalized_text = to_normalize; normalized_text.retain(|c| !c.is_whitespace()); @@ -98,11 +94,12 @@ impl Task { // TaskHandler, if the Task of the Expression is not Task::None pub struct Expression { text: String, + full_text: String, task: Task, complex: bool, - inner_value: f64, - outer_value: f64, + outer_value: Option, children: Vec, + depth: u8, } // Debug Formatter for Expression @@ -110,11 +107,12 @@ impl fmt::Debug for Expression { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Expression") .field("text", &self.text) + .field("full text", &self.full_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) + .field("depth", &self.depth) .finish() } } @@ -124,11 +122,12 @@ impl Clone for Expression{ fn clone(&self) -> Self { Expression { text: self.text.clone(), + full_text: self.full_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(), + depth: self.depth.clone(), } } } @@ -194,16 +193,24 @@ impl 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 { + pub fn new(expression_text: String, expression_full_text: String, task: Task, depth: u8) -> Expression { + + // check if we are too deep + if depth > 254 { + eprintln!("Expression '{}' has a too deep family tree. Maximum generations are 254.", expression_text); + std::process::exit(1); + } let expression_text = normalize_string(expression_text); + let mut task_text_full: String = "".to_string(); + let mut children: Vec = Vec::new(); let re_contains_sub_expression= Regex::new(r"(\(.*\))|(\[.*\])|(\{.*\})").unwrap(); + if re_contains_sub_expression.is_match(expression_text.as_str()) { let brace_groups: Vec> = find_brace_groups(expression_text.clone()); let mut brace_groups_texts: Vec = Vec::new(); - let mut children: Vec = Vec::new(); // 1 brace group per possible combination, by default, this is only (), so 1 iteration. // This is still O(n¹) @@ -219,12 +226,12 @@ impl Expression { let mut stop_at: usize = 0; // TODO check for task parameters for (index, char) in possible_task.chars().enumerate() { - stop_at = index; - if !(char.is_alphanumeric() | (char == '.') | (char == '_')) & (char == '+') { + if !(char.is_alphanumeric() | (char == '.') | (char == '_')) | (char == '+') { break; } + stop_at = index; } - let task_text_full = possible_task.clone()[..stop_at + 0].chars().rev().collect::(); + task_text_full = possible_task.clone()[..stop_at+ 1].chars().rev().collect::(); let task: Task; if task_text_full.contains('_') { let split: Vec<&str> = task_text_full.split('_').collect(); @@ -233,7 +240,9 @@ impl Expression { else { task = Task::new(task_text_full.as_str(), ""); } - let child = Expression::new(text.to_string(), task); + let child_full_text = task_text_full + "(" + text + ")"; + dbg!(&child_full_text); + let child = Expression::new(text.to_string(), child_full_text, task, depth+1); children.push(child); } } @@ -242,11 +251,12 @@ impl Expression { let expression = Expression { text: expression_text, + full_text: normalize_string(expression_full_text), task: task, complex: false, - inner_value: 0.0, - outer_value: 0.0, - children: Vec::new(), + outer_value: Some(0.0), + children: children, + depth: depth, }; #[cfg(debug_assertions)] dbg!(&expression); @@ -254,15 +264,33 @@ impl Expression { } // calculate value for expression. - pub fn process(&self) { - let normalized_text = self.normalize_text(); - // iterate through chars. Find logical groups, and add them into a hashmap. - let re_numeric = Regex::new(r"\d+(\.\d+)?"); + pub fn process(self) -> String { + let mut normalized_text = self.normalize_text(); + //let re_numeric = Regex::new(r"\d+(\.\d+)?"); + /* + * Algorithm: + * + * First, search child expressions in normalized_text by searching for the text of all + * children in normalized_text. If an expression is found, a value for it should be + * calculated (recursive!) and the text should be substituted with the calculated value. + * If a child expression is not found in the normalized_text, throw an error, as an + * expression has a child but does not contain it's text. (note: a childs child + * expressions are not the child expression of the original expression, so no need to + * worry about the order of substituting texts for values.) + * + * Once there are no more child expressions in the normalized_text, we can use the + * shunting yards algorithm to calculate the result. I'm not yet sure, if I want to use + * another developers shunting yard algorithm or implement it by myself. + */ + + // iterate through children, substitute childrens text with childrens results (as string + // slice). + for child in self.children { + dbg!(normalized_text.replace(child.full_text.as_str(), "0"/* debug only */)); + normalized_text = normalized_text.replace(child.full_text.as_str(), "0"); + } todo!(); - // no idea how to do this, seriously, this is so complicated, i wouldn't ever have thought - // this. I probably need to add a enum Operation and an enum Value to create a - // HashMap and match back the Value type to some real usable one? This is so - // complicated. + } // wrapper for normalize_string() diff --git a/src/main.rs b/src/main.rs index c3ea9d3..432e84e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,9 +46,8 @@ fn main() { 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)); + expression_vec.push(Expression::new(expression_text.clone(), expression_text, Task::None, 0)); } - for expression in expression_vec { expression.process(); } From 09c2f91ce3631b027b3dace674f0774f3bb1e161 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 Feb 2023 22:16:42 +0100 Subject: [PATCH 09/12] removed some unneeded debug, shunting yard boilerplate --- src/expression_parser.rs | 28 ++++++++-------------------- src/main.rs | 10 +++++++--- src/shunting_yard.rs | 0 3 files changed, 15 insertions(+), 23 deletions(-) create mode 100644 src/shunting_yard.rs diff --git a/src/expression_parser.rs b/src/expression_parser.rs index 4389676..cea657e 100644 --- a/src/expression_parser.rs +++ b/src/expression_parser.rs @@ -149,24 +149,12 @@ fn find_brace_groups(haystack: String) -> Vec> { for (index, char) in haystack.chars().enumerate() { match char { '(' => { - #[cfg(debug_assertions)] - { - dbg!(char); - dbg!(index); - } parenthesis_group.push((index, 0)); parenthesis_open = parenthesis_open + 1; parenthesis_last_opened.push(parenthesis_open_processed); parenthesis_open_processed = parenthesis_open_processed + 1; }, ')' => { - #[cfg(debug_assertions)] - { - dbg!(char); - dbg!(index); - dbg!(parenthesis_last_opened.len()); - dbg!(parenthesis_last_opened[parenthesis_last_opened.len() - 1]); - } parenthesis_group[parenthesis_last_opened[parenthesis_last_opened.len() - 1]].1 = index; parenthesis_open = parenthesis_open - 1; parenthesis_closed_processed = parenthesis_closed_processed + 1; @@ -241,7 +229,6 @@ impl Expression { task = Task::new(task_text_full.as_str(), ""); } let child_full_text = task_text_full + "(" + text + ")"; - dbg!(&child_full_text); let child = Expression::new(text.to_string(), child_full_text, task, depth+1); children.push(child); } @@ -254,12 +241,10 @@ impl Expression { full_text: normalize_string(expression_full_text), task: task, complex: false, - outer_value: Some(0.0), + outer_value: None, children: children, depth: depth, }; - #[cfg(debug_assertions)] - dbg!(&expression); expression } @@ -283,14 +268,17 @@ impl Expression { * another developers shunting yard algorithm or implement it by myself. */ + // TODO check if we have any unknown values. + // iterate through children, substitute childrens text with childrens results (as string // slice). for child in self.children { - dbg!(normalized_text.replace(child.full_text.as_str(), "0"/* debug only */)); - normalized_text = normalized_text.replace(child.full_text.as_str(), "0"); + normalized_text = normalized_text.replace(child.full_text.clone().as_str(), child.process().as_str()); } - todo!(); - + // TODO Shunting yards algorithm, as we now have only calculatable values left. + // Implement this as public module in shunting_yard.rs + // self.result = MYRESULT + return "RESULT_STILL_NOT_IMPLEMENTED".to_string(); } // wrapper for normalize_string() diff --git a/src/main.rs b/src/main.rs index 432e84e..2d5173b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,8 +48,12 @@ fn main() { for expression_text in expression_texts_concat { expression_vec.push(Expression::new(expression_text.clone(), expression_text, Task::None, 0)); } - for expression in expression_vec { - expression.process(); + #[cfg(debug_assertions)] + { + dbg!(&expression_vec); } - + for expression in expression_vec { + println!("{}", expression.process()); + } + } diff --git a/src/shunting_yard.rs b/src/shunting_yard.rs new file mode 100644 index 0000000..e69de29 From a518550cf1dedf0c387f6ce3c9fce1bc4754d956 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 14 Feb 2023 14:52:28 +0100 Subject: [PATCH 10/12] absolute hack of progress with shunting yard --- src/expression_parser.rs | 63 +++++++--- src/{ => expression_parser}/linear_algebra.rs | 0 src/expression_parser/shunting_yard.rs | 108 ++++++++++++++++++ src/main.rs | 10 +- src/shunting_yard.rs | 0 5 files changed, 162 insertions(+), 19 deletions(-) rename src/{ => expression_parser}/linear_algebra.rs (100%) create mode 100644 src/expression_parser/shunting_yard.rs delete mode 100644 src/shunting_yard.rs diff --git a/src/expression_parser.rs b/src/expression_parser.rs index cea657e..1268f24 100644 --- a/src/expression_parser.rs +++ b/src/expression_parser.rs @@ -1,6 +1,8 @@ -use std::fmt; +use std::{fmt, error::Error, num::IntErrorKind}; use regex::Regex; +pub mod shunting_yard; + fn normalize_string(to_normalize: String) -> String { let mut normalized_text = to_normalize; normalized_text.retain(|c| !c.is_whitespace()); @@ -80,7 +82,9 @@ impl Task { } }, // what to do if a bad task was given: - &_ => {eprintln!("Bad Task: {}", task_text); std::process::exit(1); }, + // this would be throwing an error and aborting + //&_ => {eprintln!("Bad Task: {}", task_text); std::process::exit(1); }, + _ => Task::None, } } } @@ -93,11 +97,11 @@ impl Task { // 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, + pub text: String, full_text: String, task: Task, complex: bool, - outer_value: Option, + outer_value: Result, children: Vec, depth: u8, } @@ -190,7 +194,7 @@ impl Expression { } let expression_text = normalize_string(expression_text); - let mut task_text_full: String = "".to_string(); + let mut task_text_full: String; let mut children: Vec = Vec::new(); let re_contains_sub_expression= Regex::new(r"(\(.*\))|(\[.*\])|(\{.*\})").unwrap(); @@ -206,20 +210,21 @@ impl Expression { for pair in brace_group { let text = &expression_text[pair.0..pair.1 + 1]; let text = &text[1..text.len() - 1]; - #[cfg(debug_assertions)] brace_groups_texts.push(text.to_string()); // we have the expression_text, now we just need to get the task until we can // pass these parameters into Expression::new(). This is the recursive part. let possible_task = &expression_text[..pair.0].chars().rev().collect::(); let mut stop_at: usize = 0; - // TODO check for task parameters for (index, char) in possible_task.chars().enumerate() { - if !(char.is_alphanumeric() | (char == '.') | (char == '_')) | (char == '+') { + if !(char.is_alphanumeric()) { break; } stop_at = index; } - task_text_full = possible_task.clone()[..stop_at+ 1].chars().rev().collect::(); + dbg!(&stop_at); + // needed for none task: '1 + (1 + 1)' + let fixup = if stop_at == 0 { 0 } else { 1 }; + task_text_full = possible_task.clone()[..stop_at+ fixup].chars().rev().collect::(); let task: Task; if task_text_full.contains('_') { let split: Vec<&str> = task_text_full.split('_').collect(); @@ -235,21 +240,20 @@ impl Expression { } } - let expression = Expression { text: expression_text, full_text: normalize_string(expression_full_text), - task: task, + task, complex: false, - outer_value: None, - children: children, - depth: depth, + outer_value: Err("Value not yet calculated.".to_string()), + children, + depth, }; expression } // calculate value for expression. - pub fn process(self) -> String { + pub fn process(self) -> Result { let mut normalized_text = self.normalize_text(); //let re_numeric = Regex::new(r"\d+(\.\d+)?"); /* @@ -273,12 +277,37 @@ impl Expression { // iterate through children, substitute childrens text with childrens results (as string // slice). for child in self.children { - normalized_text = normalized_text.replace(child.full_text.clone().as_str(), child.process().as_str()); + //normalized_text = normalized_text.replace(child.full_text.clone().as_str(), child.process().expect(self.text).as_str()); + let child_full_text = match child.clone().process() { + Ok(result) => result.to_string(), + Err(err) => { + eprintln!( + "Could not calculate result of child expression '{}': {}", + child.text, + "error placeholder TODO" + ); + std::process::exit(2); + } + }; + dbg!(&child.full_text); + dbg!(&child_full_text); + normalized_text = normalized_text.replace(child.full_text.as_str(), child_full_text.as_str()); } + dbg!(&normalized_text); // TODO Shunting yards algorithm, as we now have only calculatable values left. // Implement this as public module in shunting_yard.rs // self.result = MYRESULT - return "RESULT_STILL_NOT_IMPLEMENTED".to_string(); + let rpn = shunting_yard::form_reverse_polish_notation(&normalized_text); + match rpn { + Ok(valid_rpn) => { + dbg!(&valid_rpn); + return shunting_yard::calc_reverse_polish_notation(&valid_rpn); + }, + Err(err) => { + eprintln!("Could not calculate a result for expression '{}': {err}", self.text); + std::process::exit(2); + }, + } } // wrapper for normalize_string() diff --git a/src/linear_algebra.rs b/src/expression_parser/linear_algebra.rs similarity index 100% rename from src/linear_algebra.rs rename to src/expression_parser/linear_algebra.rs diff --git a/src/expression_parser/shunting_yard.rs b/src/expression_parser/shunting_yard.rs new file mode 100644 index 0000000..16bb5b7 --- /dev/null +++ b/src/expression_parser/shunting_yard.rs @@ -0,0 +1,108 @@ + +/* + * Custom made implementation of the shunting yard algorithm. + * Makes a regular mathmatical expression into reverse polish notation, + * a + b -> a b + + * a * b + c -> a b * c + + * and so on. + * these can be easily interpreted by an algorithm to calculate the value of any given term. + * + * note: this version of shunting yard does not implement functions. They are handled by the + * expression parser. + */ + +enum Associativity { + Right, + Left +} + +pub struct Operator { + character: char, + precedence: u8, + associativity: Associativity +} + +impl Operator { + pub fn is_operator(c: char) -> bool { + for op in OPERATORS { + if c == op.character { return true; } + } + return false; + } +} + +const ADDITION: Operator = Operator { + character: '+', + precedence: 2, + associativity: Associativity::Left +}; + +const SUBTRACTION: Operator = Operator { + character: '-', + precedence: 2, + associativity: Associativity::Left +}; + +const MULTIPLICATION: Operator = Operator { + character: '*', + precedence: 2, + associativity: Associativity::Left +}; + +const DIVISION: Operator = Operator { + character: '/', + precedence: 2, + associativity: Associativity::Left +}; + +const EXPONENTIATION: Operator = Operator { + character: '*', + precedence: 2, + associativity: Associativity::Left +}; + +const OPERATORS: [Operator; 5] = [ADDITION, SUBTRACTION, MULTIPLICATION, DIVISION, EXPONENTIATION]; + +pub fn form_reverse_polish_notation(regular_math: &str) -> Result { + let mut output_queue: Vec = Vec::new(); + let mut input_queue: Vec = regular_math.chars().rev().collect(); + let mut operator_stack: Vec = Vec::new(); + + // process all tokens first. + while !(input_queue.is_empty()) { + let token: char = *(input_queue.last().unwrap()); + input_queue.pop(); + dbg!(&token); + + if token.is_numeric() { + println!("number"); + } + else if Operator::is_operator(token) { + println!("operator"); + } + // Unnessecary, will be processed by the expression parser + //else if '(' == token { + // println!("("); + //} + //else if ')' == token { + // println!(")"); + //} + else { + eprintln!("Unrecognized token: '{token}'"); + std::process::exit(1); + } + + } + + // afterwards, process any operators still on the operator_stack + while !(operator_stack.is_empty()) { + todo!(); + } + + Ok("TODO".to_string()) +} + +// after we have the rpn, we may want to calculate the values with it. +pub fn calc_reverse_polish_notation(rpn: &str) -> Result { + Ok(0.0) +} diff --git a/src/main.rs b/src/main.rs index 2d5173b..c9d9782 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use clap::Parser; mod expression_parser; -mod linear_algebra; use expression_parser::Expression; use expression_parser::Task; @@ -53,7 +52,14 @@ fn main() { dbg!(&expression_vec); } for expression in expression_vec { - println!("{}", expression.process()); + match expression.clone().process() { + Ok(result) => { + println!("{result}"); + }, + Err(err) => { + eprintln!("Could not calculate expression '{}': {}", &expression.text, err); + } + } } } diff --git a/src/shunting_yard.rs b/src/shunting_yard.rs deleted file mode 100644 index e69de29..0000000 From 87b761b85cea75de573a36ea40fd65d341ea872c Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 14 Feb 2023 16:05:15 +0100 Subject: [PATCH 11/12] shunting yard works for text --- src/expression_parser/shunting_yard.rs | 95 ++++++++++++++++++++------ 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/src/expression_parser/shunting_yard.rs b/src/expression_parser/shunting_yard.rs index 16bb5b7..bb3d476 100644 --- a/src/expression_parser/shunting_yard.rs +++ b/src/expression_parser/shunting_yard.rs @@ -11,11 +11,13 @@ * expression parser. */ +#[derive(PartialEq)] enum Associativity { Right, Left } +#[derive(PartialEq)] pub struct Operator { character: char, precedence: u8, @@ -29,6 +31,17 @@ impl Operator { } return false; } + + pub fn get_operator(c: char) -> Option { + match c { + '+' => Some(ADDITION), + '-' => Some(SUBTRACTION), + '*' => Some(MULTIPLICATION), + '/' => Some(DIVISION), + '^' => Some(EXPONENTIATION), + _ => None + } + } } const ADDITION: Operator = Operator { @@ -58,7 +71,7 @@ const DIVISION: Operator = Operator { const EXPONENTIATION: Operator = Operator { character: '*', precedence: 2, - associativity: Associativity::Left + associativity: Associativity::Right }; const OPERATORS: [Operator; 5] = [ADDITION, SUBTRACTION, MULTIPLICATION, DIVISION, EXPONENTIATION]; @@ -68,38 +81,78 @@ pub fn form_reverse_polish_notation(regular_math: &str) -> Result = regular_math.chars().rev().collect(); let mut operator_stack: Vec = Vec::new(); - // process all tokens first. + // while there are tokens to br read: while !(input_queue.is_empty()) { - let token: char = *(input_queue.last().unwrap()); - input_queue.pop(); + // read a token + let token: char = input_queue.pop().unwrap(); dbg!(&token); + // if the token is: + // a number: if token.is_numeric() { - println!("number"); - } - else if Operator::is_operator(token) { - println!("operator"); - } - // Unnessecary, will be processed by the expression parser - //else if '(' == token { - // println!("("); - //} - //else if ')' == token { - // println!(")"); - //} - else { - eprintln!("Unrecognized token: '{token}'"); - std::process::exit(1); + // put it into the output_queue + output_queue.push(token); } + // a function + // handled by the expression parser + // a operator o1 + else if Operator::is_operator(token) { + // (get the constant Operator (which is a struct) that fits to that token.) + let o1 = match Operator::get_operator(token) { + Some(valid_op) => valid_op, + None => {panic!("Operator '{token}' not found.");}, + }; + + // while there is an operator o2 at the top of the stack + if !operator_stack.is_empty() { + dbg!(&operator_stack); + dbg!(&operator_stack); + let o2 = match Operator::get_operator(*(operator_stack.clone().last().clone().unwrap())) { + Some(valid_op) => valid_op, + None => {panic!("Operator '{token}' not found.");}, + }; + // and + // (o2 has greater precedence than o1 or (o1 and o2 have the same precedence and o1 + // is left associative)) + while ((operator_stack.last().is_some()) & ((o2.precedence > o1.precedence) | ((o1.precedence == o2.precedence) & (o1.associativity == Associativity::Left)))) { + dbg!(&operator_stack); + println!("REACHED THE MAGIC WHILE"); + // pop o2 from the operator stack into the output queue. + // after this debug statement, the operator_stack is empty for no reason!!!! + // FIXME + let my_c = match operator_stack.pop() { + Some(c) => c, + None => {panic!("weirdly gone!")}, + }; + output_queue.push(my_c); + } + } + operator_stack.push(o1.character); + } + /* + // Unnessecary, will be processed by the expression parser + else if '(' == token { + println!("("); + } + else if ')' == token { + println!(")"); + } + */ + else { + return Err("Unrecognized token: '{token}'".to_string()); + } } + dbg!(&output_queue); // afterwards, process any operators still on the operator_stack while !(operator_stack.is_empty()) { - todo!(); + output_queue.push(operator_stack.pop().unwrap()); } - Ok("TODO".to_string()) + dbg!(&output_queue); + let rpn: String = output_queue.iter().cloned().collect::(); + Ok(rpn) } // after we have the rpn, we may want to calculate the values with it. From a3078cf1c1b7bf100705f27d03ddb0162e3ea640 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 14 Feb 2023 16:37:35 +0100 Subject: [PATCH 12/12] shunting yard works --- Cargo.toml | 2 +- src/expression_parser.rs | 2 +- src/expression_parser/shunting_yard.rs | 51 +++++++++++++++++++------- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 39f80cf..f5d1369 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_command_line_calculator" -version = "0.1.2" +version = "0.2.0" edition = "2021" authors = ["Christoph J. Scherr "] license = "GPL3" diff --git a/src/expression_parser.rs b/src/expression_parser.rs index 1268f24..79d0e81 100644 --- a/src/expression_parser.rs +++ b/src/expression_parser.rs @@ -301,7 +301,7 @@ impl Expression { match rpn { Ok(valid_rpn) => { dbg!(&valid_rpn); - return shunting_yard::calc_reverse_polish_notation(&valid_rpn); + return shunting_yard::calc_reverse_polish_notation(valid_rpn); }, Err(err) => { eprintln!("Could not calculate a result for expression '{}': {err}", self.text); diff --git a/src/expression_parser/shunting_yard.rs b/src/expression_parser/shunting_yard.rs index bb3d476..ccc03fd 100644 --- a/src/expression_parser/shunting_yard.rs +++ b/src/expression_parser/shunting_yard.rs @@ -76,10 +76,13 @@ const EXPONENTIATION: Operator = Operator { const OPERATORS: [Operator; 5] = [ADDITION, SUBTRACTION, MULTIPLICATION, DIVISION, EXPONENTIATION]; -pub fn form_reverse_polish_notation(regular_math: &str) -> Result { - let mut output_queue: Vec = Vec::new(); +pub fn form_reverse_polish_notation(regular_math: &str) -> Result, String> { + let mut output_queue: Vec> = Vec::new(); let mut input_queue: Vec = regular_math.chars().rev().collect(); let mut operator_stack: Vec = Vec::new(); + let mut currently_processing_numeric_group = false; + let mut current_numeric_group: Vec = Vec::new(); + let mut current_numeric_group_has_point = false; // while there are tokens to br read: while !(input_queue.is_empty()) { @@ -89,35 +92,48 @@ pub fn form_reverse_polish_notation(regular_math: &str) -> Result valid_op, - None => {panic!("Operator '{token}' not found.");}, + None => {panic!("Operator '{}' not found.", token);}, }; // while there is an operator o2 at the top of the stack if !operator_stack.is_empty() { - dbg!(&operator_stack); dbg!(&operator_stack); let o2 = match Operator::get_operator(*(operator_stack.clone().last().clone().unwrap())) { Some(valid_op) => valid_op, - None => {panic!("Operator '{token}' not found.");}, + None => {panic!("Operator '{}' not found.", token);}, }; // and // (o2 has greater precedence than o1 or (o1 and o2 have the same precedence and o1 // is left associative)) while ((operator_stack.last().is_some()) & ((o2.precedence > o1.precedence) | ((o1.precedence == o2.precedence) & (o1.associativity == Associativity::Left)))) { - dbg!(&operator_stack); - println!("REACHED THE MAGIC WHILE"); // pop o2 from the operator stack into the output queue. // after this debug statement, the operator_stack is empty for no reason!!!! // FIXME @@ -125,7 +141,7 @@ pub fn form_reverse_polish_notation(regular_math: &str) -> Result c, None => {panic!("weirdly gone!")}, }; - output_queue.push(my_c); + output_queue.push(vec![my_c]); } } operator_stack.push(o1.character); @@ -140,22 +156,29 @@ pub fn form_reverse_polish_notation(regular_math: &str) -> Result(); + let mut rpn: Vec = Vec::new(); + for group in output_queue { + rpn.push(group.iter().cloned().collect::()); + } Ok(rpn) } // after we have the rpn, we may want to calculate the values with it. -pub fn calc_reverse_polish_notation(rpn: &str) -> Result { +pub fn calc_reverse_polish_notation(rpn: Vec) -> Result { Ok(0.0) }