From c590bde0f877608df667ef64bcb65868dc544134 Mon Sep 17 00:00:00 2001 From: geo-ant <54497890+geo-ant@users.noreply.github.com> Date: Mon, 19 Apr 2021 21:20:11 +0200 Subject: [PATCH 1/3] simplify macros and append documentation --- README.md | 22 +++++++------ check_ci.sh | 2 +- fluent-comparisons-macros/Cargo.toml | 1 + fluent-comparisons-macros/README.md | 2 ++ fluent-comparisons-macros/src/lib.rs | 42 +++--------------------- src/lib.rs | 49 ++++++++++++++++++++++++++-- 6 files changed, 66 insertions(+), 52 deletions(-) create mode 100644 fluent-comparisons-macros/README.md diff --git a/README.md b/README.md index eb6f4f1..f8e5598 100644 --- a/README.md +++ b/README.md @@ -3,19 +3,21 @@ ![tests](https://github.com/geo-ant/fluent-comparisons/workflows/tests/badge.svg?branch=main) ![lints](https://github.com/geo-ant/fluent-comparisons/workflows/lints/badge.svg?branch=main) -A crate offering a fluent syntax for multi-comparisons. +**Fluent syntax for multi-comparisons.** ## Motivation -This crate is everyone for you if you have ever gotten annoyed at writing conditions like this +This crate is for you if you have ever gotten annoyed at writing repetitive conditions like this ```rust if x < a && y < a && z < a { // ... do something } ``` -and wished you could replace that code by something more expressive and less repetitive? If so, then this crate is for you, because it allows us to write the condition above as +and wished you could replace that code by something more expressive and less repetitive? Now you can write the condition above as ```rust +use fluent_comparisons::all_of; + if all_of!({x,y,z} < a) { // ... do something } @@ -23,28 +25,28 @@ if all_of!({x,y,z} < a) { ## Brief Description and Key Advantages -The crate provides the macros `any_of`, `all_of` and `none_of` to facilitate writing conditions analogous to the example above. Additional to offering an intuitive syntax and more readable code, the macros compile down to the same assembly as the handwritten code for any nonzero optimization level ([check it on godbolt.org](https://godbolt.org/z/zTfTq6va5)). +The crate provides the macros `any_of`, `all_of` and `none_of` to facilitate writing expressive multicomparisons. In addition to providing an intuitive syntax, the macros compile to the same assembly as the handwritten code ([check it on godbolt.org](https://godbolt.org/z/M3494a6Mc)). A further benefit is [lazy evaluation](https://doc.rust-lang.org/reference/expressions/operator-expr.html#lazy-boolean-operators) from left to right as seen in the next snippet: ```rust - // if cheap_calc(arg1) <=5, then the costly calculation + // if cheap_calc(arg1) <=5, then the expensive calculation // is never performed - let cond = any_of!({cheap_calc(arg1), expensive_calc(arg2)}<=5); + any_of!({cheap_calc(arg1), expensive_calc(arg2)}<=5) // whereas if we did this, the expensive calculation would be // performed regardless of the result of cheap_calculation(arg1) - [cheap_calculation(arg1), expensive_calculation(arg2)].iter().any(|val|val<=5) + [cheap_calc(arg1), expensive_calc(arg2)].iter().any(|val|val<=&5) ``` ## Usage -The macros are called as `any_of!({/*list of expressions*/} operator rhs)`, where operator can be any of the binary comparison operators, i.e. `==`, `!=`, `<=`, `<`, `>`, and `>=`. The list of expressions on the left hand side is comma separated without a trailing comma. The right hand side is an expression as well. +Use the macros by writing `any_of!({/*list of expressions*/} operator rhs)`, where operator can be any of the binary comparison operators, i.e. `==`, `!=`, `<=`, `<`, `>`, and `>=`. The list of expressions on the left hand side is comma separated without a trailing comma. The right hand side is an expression as well. -The list of expressions can have a variadic number of elements but must have at least one. It must always be enclosed in curly braces. The expressions on the left hand side need not be of the same type, but the comparison with the right hand side must be valid. In particular, the expressions need not be numeric. +The list of expressions can have a variadic number of elements but must have at least one. It must always be enclosed in curly braces. The expressions on the left hand side need not be of the same type, but the comparison with the right hand side must be valid individually. Furthermore, the expressions can evaluate to anything that can be compared to `rhs`, not just numbers. The same goes for the `all_of` and `none_of` macros. Check the docs for more information. ## Links -This library is inspired by Björn Fahller's [DRY comparisons](https://github.com/rollbear/dry-comparisons) library, which I read about [in this blog post](https://www.fluentcpp.com/2020/01/03/dry-comparisons-a-c-library-to-shorten-redundant-if-statements/) on Jonathan Boccara's blog. \ No newline at end of file +This library is inspired by Björn Fahller's [DRY comparisons](https://github.com/rollbear/dry-comparisons) Modern C++ library, which I read about [in this blog post](https://www.fluentcpp.com/2020/01/03/dry-comparisons-a-c-library-to-shorten-redundant-if-statements/) on Jonathan Boccara's blog. \ No newline at end of file diff --git a/check_ci.sh b/check_ci.sh index 5e1f58d..d9ff8c4 100755 --- a/check_ci.sh +++ b/check_ci.sh @@ -2,4 +2,4 @@ # echo on cargo fmt cargo clippy --all-targets --all-features -- -D warnings -cargo test +cargo test --workspace diff --git a/fluent-comparisons-macros/Cargo.toml b/fluent-comparisons-macros/Cargo.toml index aaa3443..69a821d 100644 --- a/fluent-comparisons-macros/Cargo.toml +++ b/fluent-comparisons-macros/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/geo-ant/fluent-comparisons" description = "Macros for the fluent-comparisons crate." categories = ["algorithms", "development tools"] license = "MIT" +readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/fluent-comparisons-macros/README.md b/fluent-comparisons-macros/README.md new file mode 100644 index 0000000..b0727e9 --- /dev/null +++ b/fluent-comparisons-macros/README.md @@ -0,0 +1,2 @@ +# fluent-comparisons-macros +Macros for the [fluent-comparisons](https://crates.io/crates/fluent-comparisons) crate. \ No newline at end of file diff --git a/fluent-comparisons-macros/src/lib.rs b/fluent-comparisons-macros/src/lib.rs index 765caee..7f721fb 100644 --- a/fluent-comparisons-macros/src/lib.rs +++ b/fluent-comparisons-macros/src/lib.rs @@ -52,23 +52,12 @@ macro_rules! __check_operator { /// ``` #[macro_export] macro_rules! any_of { - //TODO CAUTION: THIS COULD BE CALLED WITH ONE ARGUMENT. MAKE SURE THAT THIS PRODUCES A VALID RESULT - // expression like any_of!( {1,v.len(),4} < 3) ( {$($lh_sides:expr),+} $operator:tt $rhs:expr)=> { { $crate::__check_operator!($operator); - any_of!(@internal lhs={$($lh_sides),+}, op=[$operator], rhs = $rhs, expanded = {false}) + $( ($lh_sides $operator $rhs) )||+ } }; - // internal rules, recursion final case - (@internal lhs = {$head:expr},op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) => { - $expanded || ($head $op $rhs) - }; - - // internal rules, recursion case - (@internal lhs = {$head:expr, $($tail:expr),*}, op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) =>{ - any_of!(@internal lhs={$($tail),*}, op=[$op], rhs = $rhs, expanded = {$expanded || ($head $op $rhs)}) - }; } /// Compare all values in a set to a common right hand side and decide whether the comparison returns `true` for *all of the values* in the set. @@ -101,24 +90,12 @@ macro_rules! any_of { /// ``` #[macro_export] macro_rules! all_of { - //TODO CAUTION: THIS COULD BE CALLED WITH ONE ARGUMENT. MAKE SURE THAT THIS PRODUCES A VALID RESULT - // expression like any_of!( {1,v.len(),4} < 3) ( {$($lh_sides:expr),+} $operator:tt $rhs:expr)=> { { $crate::__check_operator!($operator); - all_of!(@internal lhs={$($lh_sides),+}, op=[$operator], rhs = $rhs, expanded = {true}) + $( ($lh_sides $operator $rhs) )&&+ } }; - - // internal rules, recursion final case - (@internal lhs = {$head:expr},op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) => { - $expanded && ($head $op $rhs) - }; - - // internal rules, recursion case - (@internal lhs = {$head:expr, $($tail:expr),*}, op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) =>{ - all_of!(@internal lhs={$($tail),*}, op=[$op], rhs = $rhs, expanded = {$expanded && ($head $op $rhs)}) - }; } /// Compare all values in a set to a common right hand side and decide whether the comparison returns `true` for *none of the values* in the set. @@ -151,27 +128,16 @@ macro_rules! all_of { /// ``` #[macro_export] macro_rules! none_of { - //TODO CAUTION: THIS COULD BE CALLED WITH ONE ARGUMENT. MAKE SURE THAT THIS PRODUCES A VALID RESULT - // expression like any_of!( {1,v.len(),4} < 3) ( {$($lh_sides:expr),+} $operator:tt $rhs:expr)=> { { $crate::__check_operator!($operator); - none_of!(@internal lhs={$($lh_sides),+}, op=[$operator], rhs = $rhs, expanded = {true}) + $( !($lh_sides $operator $rhs) )&&+ } }; - - // internal rules, recursion final case - (@internal lhs = {$head:expr},op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) => { - $expanded && !($head $op $rhs) - }; - - // internal rules, recursion case - (@internal lhs = {$head:expr, $($tail:expr),*}, op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) =>{ - none_of!(@internal lhs={$($tail),*}, op=[$op], rhs = $rhs, expanded = {$expanded && !($head $op $rhs)}) - }; } // TODO FINISH THIS UP, TEST IT AND DOCUMENT IT +// TODO: make this as simple as the ones above w/o recursion // #[macro_export] // macro_rules! exactly_one_of { // //TODO CAUTION: THIS COULD BE CALLED WITH ONE ARGUMENT. MAKE SURE THAT THIS PRODUCES A VALID RESULT diff --git a/src/lib.rs b/src/lib.rs index 4a18b43..cd29faf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,49 @@ -//! This library provides a number of macros to make multicomparison expressions less annoying to -//! write while still keeping the benefits of hand-written code, such as lazy evaluation and -//! boolean short circuiting. Go to any of the macros to find out about their usage. +//! This crate is for you if you have ever gotten annoyed at writing repetitive conditions like this +//! ```rust +//! # fn test(a:i32,x:i32,y:i32,z:i32) { +//! if x < a && y < a && z < a { +//! // ... do something +//! } +//! # } +//! ``` +//! and wished you could replace that code by something more expressive and less repetitive? Now you can rewrite the code as +//! ```rust +//!# fn test(a:i32,x:i32,y:i32,z:i32) { +//! use fluent_comparisons::all_of; +//! +//! if all_of!({x,y,z} < a) { +//! // ... do something +//! } +//! # } +//! ``` +//! +//! ## Brief Description and Key Advantages +//! +//! The crate provides the macros `any_of`, `all_of` and `none_of` to facilitate writing expressive multicomparisons. +//! In addition to providing an intuitive syntax, the macros compile to the same assembly as +//! the handwritten code ([check it on godbolt.org](https://godbolt.org/z/M3494a6Mc)). +//! +//! A further benefit is [lazy evaluation](https://doc.rust-lang.org/reference/expressions/operator-expr.html#lazy-boolean-operators) from +//! left to right as seen in the next snippet: +//! +//! ```rust +//! use fluent_comparisons::any_of; +//! +//! # fn cheap_calc(v : usize)-> usize {v} +//! # fn expensive_calc(v : usize)-> usize {v} +//! # fn test(arg1: usize, arg2:usize) { +//! // if cheap_calc(arg1) <=5, then the expensive calculation +//! // is never performed +//! let b = any_of!({cheap_calc(arg1), expensive_calc(arg2)}<=5); +//! // whereas if we did this, the expensive calculation would be +//! // performed regardless of the result of cheap_calculation(arg1) +//! let b = [cheap_calc(arg1), expensive_calc(arg2)].iter().any(|val|val<=&5); +//! # } +//! ``` +//! +//! ## Usage +//! +//! Refer to the items in the documentation below to learn about the usage of the macros. pub use fluent_comparisons_macros::any_of; From 3d3f6420a19e071444bd5a94ca9f3378c41e98b8 Mon Sep 17 00:00:00 2001 From: geo-ant <54497890+geo-ant@users.noreply.github.com> Date: Mon, 19 Apr 2021 21:23:27 +0200 Subject: [PATCH 2/3] proofread docs --- README.md | 4 ++-- src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f8e5598..a445cb4 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,14 @@ ## Motivation -This crate is for you if you have ever gotten annoyed at writing repetitive conditions like this +This crate is for you if you have ever been annoyed at writing repetitive conditions like this ```rust if x < a && y < a && z < a { // ... do something } ``` -and wished you could replace that code by something more expressive and less repetitive? Now you can write the condition above as +and wished you could replace that code by something more expressive and less repetitive. Now you can write the condition above as ```rust use fluent_comparisons::all_of; diff --git a/src/lib.rs b/src/lib.rs index cd29faf..0753b13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -//! This crate is for you if you have ever gotten annoyed at writing repetitive conditions like this +//! This crate is for you if you have ever been annoyed at writing repetitive conditions like this //! ```rust //! # fn test(a:i32,x:i32,y:i32,z:i32) { //! if x < a && y < a && z < a { @@ -6,7 +6,7 @@ //! } //! # } //! ``` -//! and wished you could replace that code by something more expressive and less repetitive? Now you can rewrite the code as +//! and wished you could replace that code by something more expressive and less repetitive. Now you can rewrite the code as //! ```rust //!# fn test(a:i32,x:i32,y:i32,z:i32) { //! use fluent_comparisons::all_of; From 76dbf9cc8b1d43be04d1fa77b60b693f24786858 Mon Sep 17 00:00:00 2001 From: geo-ant <54497890+geo-ant@users.noreply.github.com> Date: Mon, 19 Apr 2021 21:25:35 +0200 Subject: [PATCH 3/3] bump version numbers --- Cargo.toml | 4 ++-- fluent-comparisons-macros/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f4664d8..fc84f32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fluent-comparisons" -version = "0.1.1" +version = "0.2.0" authors = ["geo-ant "] edition = "2018" homepage = "https://github.com/geo-ant/fluent-comparisons" @@ -18,7 +18,7 @@ keywords = ["fluent","DRY","multi","comparison","expressions"] # be used, but on crates.io the version dependency will be used, which is just extremely helpful for development. # See this Reddit post: https://www.reddit.com/r/rust/comments/a39er8/how_do_you_publish_your_cargo_workspace_packages/ # And the Rust Doc: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-path-dependencies -fluent-comparisons-macros = {version = "0.1.0", path = "fluent-comparisons-macros"} +fluent-comparisons-macros = {version = "0.2.0", path = "fluent-comparisons-macros"} [workspace] members = ["fluent-comparisons-macros"] diff --git a/fluent-comparisons-macros/Cargo.toml b/fluent-comparisons-macros/Cargo.toml index 69a821d..8c32f42 100644 --- a/fluent-comparisons-macros/Cargo.toml +++ b/fluent-comparisons-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fluent-comparisons-macros" -version = "0.1.0" +version = "0.2.0" authors = ["geo-ant "] edition = "2018" homepage = "https://github.com/geo-ant/fluent-comparisons"