├── .gitignore ├── join ├── .gitignore ├── Cargo.toml ├── LICENSE └── tests │ ├── join.rs │ ├── join_spawn.rs │ ├── join_async_spawn.rs │ └── join_async.rs ├── join_impl ├── .gitignore ├── src │ ├── parse │ │ ├── mod.rs │ │ ├── empty.rs │ │ ├── unit.rs │ │ └── utils.rs │ ├── chain │ │ ├── group │ │ │ ├── mod.rs │ │ │ ├── types.rs │ │ │ ├── expr_group.rs │ │ │ ├── group_determiner.rs │ │ │ ├── combinator.rs │ │ │ └── action_group.rs │ │ ├── parse_chain.rs │ │ ├── expr │ │ │ ├── mod.rs │ │ │ ├── action_expr.rs │ │ │ ├── initial_expr.rs │ │ │ ├── err_expr.rs │ │ │ └── macros.rs │ │ └── mod.rs │ ├── lib.rs │ ├── join │ │ ├── config.rs │ │ ├── mod.rs │ │ ├── name_constructors.rs │ │ └── parse.rs │ ├── action_expr_chain │ │ ├── mod.rs │ │ └── builder.rs │ └── handler.rs ├── Cargo.toml └── LICENSE ├── rustfmt.toml ├── Cargo.toml ├── .github └── workflows │ ├── clippy.yml │ ├── lints.yml │ └── test.yml ├── LICENSE └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | target/** 2 | .DS_Store -------------------------------------------------------------------------------- /join/.gitignore: -------------------------------------------------------------------------------- 1 | target/** 2 | .DS_Store -------------------------------------------------------------------------------- /join_impl/.gitignore: -------------------------------------------------------------------------------- 1 | target/** 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Imports 2 | imports_granularity = "Crate" 3 | reorder_imports = true 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "join", 5 | "join_impl" 6 | ] 7 | -------------------------------------------------------------------------------- /join_impl/src/parse/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Misc parsing utils. 3 | //! 4 | pub mod empty; 5 | pub mod unit; 6 | pub mod utils; 7 | 8 | pub use empty::Empty; 9 | -------------------------------------------------------------------------------- /join_impl/src/chain/group/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! All expression group definitions. 3 | //! 4 | 5 | mod action_group; 6 | mod combinator; 7 | mod expr_group; 8 | mod group_determiner; 9 | mod types; 10 | 11 | pub use action_group::*; 12 | pub use combinator::*; 13 | pub use expr_group::*; 14 | pub use group_determiner::*; 15 | pub use types::*; 16 | -------------------------------------------------------------------------------- /join_impl/src/chain/parse_chain.rs: -------------------------------------------------------------------------------- 1 | use super::Chain; 2 | use syn::parse::ParseStream; 3 | 4 | /// 5 | /// Build chain from `ParseStream` 6 | /// 7 | pub trait ParseChain { 8 | /// 9 | /// Builds `T` from input `ParseStream`. 10 | /// 11 | fn build_from_parse_stream(&self, input: ParseStream<'_>) -> syn::Result; 12 | } 13 | -------------------------------------------------------------------------------- /join_impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Implementation of the `join!` macro. 3 | //! 4 | #![allow(clippy::large_enum_variant)] 5 | 6 | extern crate alloc; 7 | extern crate proc_macro; 8 | extern crate proc_macro2; 9 | extern crate quote; 10 | extern crate syn; 11 | 12 | pub mod action_expr_chain; 13 | pub mod chain; 14 | pub mod handler; 15 | pub mod join; 16 | pub mod parse; 17 | 18 | pub use crate::join::{generate_join, Config, JoinInputDefault}; 19 | -------------------------------------------------------------------------------- /join_impl/src/parse/empty.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Empty parsing unit with all necessary traits implemented. 3 | //! 4 | 5 | use syn::parse::{Parse, ParseStream}; 6 | 7 | /// 8 | /// Struct which parses empty `ParseStream` as `Ok`, non-empty as `Err`. 9 | /// 10 | #[derive(PartialEq, Eq, Clone, Debug)] 11 | pub struct Empty; 12 | 13 | impl Parse for Empty { 14 | fn parse(input: ParseStream) -> syn::Result { 15 | if input.is_empty() { 16 | Ok(Self) 17 | } else { 18 | Err(input.error("Unexpected tokens")) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /join_impl/src/join/config.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Configuration of the `join!` macro. 3 | //! 4 | 5 | /// 6 | /// Defines configuration of the `join!` macro. 7 | /// 8 | pub struct Config { 9 | /// 10 | /// Are branches sync or futures. 11 | /// 12 | pub is_async: bool, 13 | /// 14 | /// Transpose final values into one `Result`/`Option` and check results at the end of step. 15 | /// 16 | pub is_try: bool, 17 | /// 18 | /// Spawn branches using `std::thread::spawn` for sync or `tokio::spawn` for futures. 19 | /// 20 | pub is_spawn: bool, 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Clippy 4 | 5 | jobs: 6 | lints: 7 | name: Clippy 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | 13 | - name: Install stable toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: nightly 18 | override: true 19 | components: clippy 20 | 21 | - name: Run cargo clippy 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: clippy 25 | args: --all 26 | -------------------------------------------------------------------------------- /.github/workflows/lints.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Lints 4 | 5 | jobs: 6 | lints: 7 | name: Lints 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | 13 | - name: Install stable toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: nightly 18 | override: true 19 | components: rustfmt 20 | 21 | - name: Run cargo fmt 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: fmt 25 | args: --all -- --check 26 | -------------------------------------------------------------------------------- /join_impl/src/chain/group/types.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! `ActionGroup` types definitions. 3 | //! 4 | 5 | /// 6 | /// `ApplicationType` defines two types of action: `Instant` and `Deferred`. `Instant` means that action will be applied 7 | /// instantly, deferred means that it will wait for all actions in current step to be finished. 8 | /// 9 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 10 | pub enum ApplicationType { 11 | Instant, 12 | Deferred, 13 | } 14 | 15 | /// 16 | /// `MoveType` defines nested combinator types. 17 | /// 18 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 19 | pub enum MoveType { 20 | Wrap, 21 | Unwrap, 22 | None, 23 | } 24 | -------------------------------------------------------------------------------- /join_impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "join_impl" 3 | version = "0.3.1" 4 | authors = ["olegnn "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Implementation of the `join!` macro." 8 | repository = "https://github.com/olegnn/join" 9 | documentation = "https://docs.rs/join_impl" 10 | readme = "README.md" 11 | 12 | [lib] 13 | name = "join_impl" 14 | 15 | [features] 16 | full = [] 17 | 18 | [dependencies] 19 | proc-macro2 = { version = "1.0.3", default-features = false } 20 | quote = { version = "1.0.2", default-features = false } 21 | syn = { version = "1.0", default-features = false, features = ["derive", "full", "extra-traits"] } 22 | lazy_static = "1.4.0" 23 | 24 | [dev-dependencies] 25 | join = { path = "../join", version = "0.3.0" } 26 | 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Test 4 | 5 | jobs: 6 | test: 7 | name: Tests 8 | env: 9 | TARGET_CC: clang 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout sources 13 | uses: actions/checkout@v2 14 | 15 | - name: Install Build Dependencies 16 | run: sudo apt-get update && sudo apt-get install pkg-config libssl-dev clang 17 | 18 | - name: Install stable toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | target: x86_64-unknown-linux-gnu 24 | override: true 25 | 26 | - name: Run cargo test 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: test 30 | args: --all 31 | 32 | -------------------------------------------------------------------------------- /join/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "join" 3 | version = "0.3.1" 4 | authors = ["olegnn "] 5 | license = "MIT" 6 | description = """ 7 | Macros which provide useful shortcut combinators, combine sync/async chains, 8 | support single and multi thread (sync/async) step by step execution of branches, 9 | transform tuple of results in result of tuple. 10 | """ 11 | edition = "2018" 12 | repository = "https://github.com/olegnn/join" 13 | documentation = "https://docs.rs/join" 14 | readme = "README.md" 15 | categories = ["asynchronous", "rust-patterns", "concurrency"] 16 | 17 | [lib] 18 | name = "join" 19 | proc-macro = true 20 | 21 | [features] 22 | 23 | [dependencies] 24 | join_impl = { path = "../join_impl", version = "0.3.1" } 25 | syn = { version = "1.0", features = ["full", "extra-traits"] } 26 | 27 | [dev-dependencies] 28 | futures = "0.3.0" 29 | tokio = { version = "1.0.1", features = ["full"] } 30 | failure = "0.1.6" 31 | futures-timer = "0.4.0" 32 | reqwest = "0.11.0" 33 | rand = "0.7.2" 34 | rayon = "1.2.1" 35 | -------------------------------------------------------------------------------- /join_impl/src/chain/expr/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! All expression definitions and `InnerExpr` trait. 3 | //! 4 | 5 | use syn::Expr; 6 | 7 | mod err_expr; 8 | mod initial_expr; 9 | mod process_expr; 10 | 11 | mod action_expr; 12 | mod macros; 13 | 14 | pub use action_expr::ActionExpr; 15 | pub use err_expr::ErrExpr; 16 | pub use initial_expr::InitialExpr; 17 | pub use process_expr::ProcessExpr; 18 | 19 | /// 20 | /// Provide functionality to get or replace inner `Expr`(s). 21 | /// 22 | pub trait InnerExpr 23 | where 24 | Self: Sized, 25 | { 26 | /// 27 | /// Extracts `Expr`(s) from given value if applicable. 28 | /// 29 | fn inner_exprs(&self) -> Option<&[Expr]>; 30 | 31 | /// 32 | /// Replaces current expr by given `Expr` if it's possible, returning Some(`Self`) with given `Expr`(s), 33 | /// otherwise returns `None`. 34 | /// 35 | fn replace_inner_exprs(self, expr: &[Expr]) -> Option; 36 | 37 | /// 38 | /// Checks if expr can be replaced. 39 | /// 40 | fn is_replaceable(&self) -> bool { 41 | true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-Present Oleg Nosov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /join/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-Present Oleg Nosov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /join_impl/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-Present Oleg Nosov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /join_impl/src/chain/expr/action_expr.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Enum of all expression types. 3 | //! 4 | 5 | use super::{ErrExpr, InitialExpr, InnerExpr, ProcessExpr}; 6 | use syn::Expr; 7 | 8 | /// 9 | /// One of either `Process`, `Err` or `Initial` expression. 10 | /// 11 | #[derive(Clone, PartialEq, Eq, Debug)] 12 | pub enum ActionExpr { 13 | Process(ProcessExpr), 14 | Err(ErrExpr), 15 | Initial(InitialExpr), 16 | } 17 | 18 | impl InnerExpr for ActionExpr { 19 | fn replace_inner_exprs(self, exprs: &[Expr]) -> Option { 20 | Some(match self { 21 | ActionExpr::Process(expr) => ActionExpr::Process(expr.replace_inner_exprs(exprs)?), 22 | ActionExpr::Err(expr) => ActionExpr::Err(expr.replace_inner_exprs(exprs)?), 23 | ActionExpr::Initial(expr) => ActionExpr::Initial(expr.replace_inner_exprs(exprs)?), 24 | }) 25 | } 26 | 27 | fn inner_exprs(&self) -> Option<&[Expr]> { 28 | match self { 29 | ActionExpr::Process(expr) => expr.inner_exprs(), 30 | ActionExpr::Err(expr) => expr.inner_exprs(), 31 | ActionExpr::Initial(expr) => expr.inner_exprs(), 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /join_impl/src/chain/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Generic chain with all kinds of expressions, groups etc. 3 | //! 4 | 5 | pub mod expr; 6 | pub mod group; 7 | pub mod parse_chain; 8 | pub use parse_chain::ParseChain; 9 | 10 | /// 11 | /// Any chain with members of type `Member` and optional `Identifier`. 12 | /// 13 | pub trait Chain 14 | where 15 | Self: Sized, 16 | { 17 | type Member: Sized; 18 | type Identifier: Sized; 19 | 20 | /// 21 | /// Constructs new Chain. 22 | /// 23 | fn new(id: impl Into>, members: &[Self::Member]) -> Self; 24 | 25 | /// 26 | /// Adds member to chain. 27 | /// 28 | fn append_member(&mut self, member: Self::Member) -> usize; 29 | 30 | /// 31 | /// Removes member from chain. 32 | /// 33 | fn remove_member(&mut self, idx: usize) -> Option; 34 | 35 | /// 36 | /// Returns self members. 37 | /// 38 | fn members(&self) -> &[Self::Member]; 39 | 40 | /// 41 | /// Returns chain length. 42 | /// 43 | fn len(&self) -> usize; 44 | 45 | /// 46 | /// Checks if chain is empty. 47 | /// 48 | fn is_empty(&self) -> bool { 49 | self.len() == 0 50 | } 51 | 52 | /// 53 | /// Sets chain identifier. 54 | /// 55 | fn set_id(&mut self, id: impl Into>) -> &mut Self; 56 | 57 | /// 58 | /// Returns optional `Identifier` associated with chain. 59 | /// 60 | fn id(&self) -> Option<&Self::Identifier>; 61 | } 62 | -------------------------------------------------------------------------------- /join_impl/src/parse/unit.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! `Unit` module describes one unit of expression parsing which contains currently parsed `T` 3 | //! and optional next `N` 4 | //! 5 | //! 6 | use core::fmt::Debug; 7 | use syn::parse::{Parse, ParseStream}; 8 | 9 | /// 10 | /// Defines one unit of expression parsing. 11 | /// 12 | #[derive(Debug, Clone)] 13 | pub struct Unit { 14 | /// 15 | /// Parsed value (for example `Expr` { Ok(1) }). 16 | /// 17 | pub parsed: T, 18 | /// 19 | /// Next `N` if provided. 20 | /// 21 | pub next: Option, 22 | } 23 | 24 | /// 25 | /// Defines `Result` of one unit expression parsing. 26 | /// 27 | pub type UnitResult = syn::Result>; 28 | 29 | pub trait ParseUnit { 30 | /// 31 | /// Parses input stream until next `N`. 32 | /// 33 | fn parse_unit( 34 | &self, 35 | input: ParseStream, 36 | allow_empty_parsed: bool, 37 | ) -> UnitResult; 38 | } 39 | 40 | /// 41 | /// Allows to map `Self` over some parsed. 42 | /// 43 | pub trait MapParsed { 44 | type Output: Clone + Debug; 45 | 46 | fn map_parsed(self, f: F) -> Self::Output 47 | where 48 | F: FnOnce(T) -> R; 49 | } 50 | 51 | impl MapParsed for UnitResult { 52 | type Output = UnitResult; 53 | 54 | fn map_parsed(self, f: F) -> Self::Output 55 | where 56 | F: FnOnce(T) -> R, 57 | { 58 | self.map(|Unit { parsed, next }| Unit { 59 | parsed: f(parsed), 60 | next, 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /join_impl/src/action_expr_chain/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! `Chain` implemented for `ExprGroup` . 3 | //! 4 | pub mod builder; 5 | 6 | use crate::chain::{expr::ActionExpr, group::ExprGroup, Chain}; 7 | pub use builder::ActionExprChainBuilder; 8 | use syn::PatIdent; 9 | 10 | /// 11 | /// Chain with members of type `ExprGroup` and optional `PatIdent` identifier. 12 | /// 13 | pub struct ActionExprChain { 14 | ident: Option, 15 | members: Vec>, 16 | } 17 | 18 | /// 19 | /// Implementation of `Chain` with `ExprGroup` members. 20 | /// 21 | impl Chain for ActionExprChain 22 | where 23 | Self: Sized, 24 | { 25 | type Member = ExprGroup; 26 | type Identifier = PatIdent; 27 | 28 | fn new(ident: impl Into>, members: &[ExprGroup]) -> Self { 29 | Self { 30 | ident: ident.into(), 31 | members: members.to_vec(), 32 | } 33 | } 34 | 35 | fn append_member(&mut self, val: ExprGroup) -> usize { 36 | self.members.push(val); 37 | self.members.len() 38 | } 39 | 40 | fn set_id(&mut self, val: impl Into>) -> &mut Self { 41 | self.ident = val.into(); 42 | self 43 | } 44 | 45 | fn members(&self) -> &[Self::Member] { 46 | &self.members 47 | } 48 | 49 | fn id(&self) -> Option<&Self::Identifier> { 50 | self.ident.as_ref() 51 | } 52 | 53 | fn remove_member(&mut self, idx: usize) -> Option { 54 | if idx < self.len() { 55 | Some(self.members.remove(idx)) 56 | } else { 57 | None 58 | } 59 | } 60 | 61 | fn len(&self) -> usize { 62 | self.members.len() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /join_impl/src/chain/expr/initial_expr.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! `InitialExpr` definition. 3 | //! 4 | 5 | use proc_macro2::TokenStream; 6 | use quote::{quote, ToTokens}; 7 | use syn::Expr; 8 | 9 | use super::{ActionExpr, InnerExpr}; 10 | 11 | /// 12 | /// Used to define expression which is the start value in chain. 13 | /// 14 | #[derive(Clone, PartialEq, Eq, Debug)] 15 | pub enum InitialExpr { 16 | Single([Expr; 1]), 17 | } 18 | 19 | impl ToTokens for InitialExpr { 20 | fn to_tokens(&self, output: &mut TokenStream) { 21 | let expr = self.inner_exprs().unwrap(); 22 | let tokens = quote! { #( #expr )* }; 23 | output.extend(tokens); 24 | } 25 | } 26 | 27 | impl InnerExpr for InitialExpr { 28 | fn inner_exprs(&self) -> Option<&[Expr]> { 29 | match self { 30 | Self::Single(expr) => Some(expr), 31 | } 32 | } 33 | 34 | fn replace_inner_exprs(self, exprs: &[Expr]) -> Option { 35 | exprs.last().cloned().map(|expr| Self::Single([expr])) 36 | } 37 | } 38 | 39 | impl From for ActionExpr { 40 | fn from(val: InitialExpr) -> Self { 41 | ActionExpr::Initial(val) 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | use syn::{parse_quote, Expr}; 49 | 50 | fn are_streams_equal(a: TokenStream, b: TokenStream) -> bool { 51 | format!("{:#?}", a) == format!("{:#?}", b) 52 | } 53 | 54 | #[test] 55 | fn it_tests_inner_expr_trait_impl_for_err_expr() { 56 | let expr: Expr = parse_quote! { |v| v + 1 }; 57 | 58 | assert_eq!( 59 | InitialExpr::Single([expr.clone()]).inner_exprs().clone(), 60 | Some(&[expr][..]) 61 | ); 62 | } 63 | 64 | #[test] 65 | fn it_tests_inner_expr_trait_impl_replace_inner_for_initial_expr() { 66 | let expr: Expr = parse_quote! { |v| v + 1 }; 67 | let replace_inner: Expr = parse_quote! { |v| 1 + v }; 68 | 69 | assert_eq!( 70 | InitialExpr::Single([expr]) 71 | .replace_inner_exprs(&[replace_inner.clone()][..]) 72 | .unwrap() 73 | .inner_exprs() 74 | .clone(), 75 | Some(&[replace_inner][..]) 76 | ); 77 | } 78 | 79 | #[test] 80 | fn it_tests_to_tokens_trait_impl_for_initial_expr() { 81 | let expr: Expr = parse_quote! { |v| v + 1 }; 82 | 83 | assert!(are_streams_equal( 84 | InitialExpr::Single([expr.clone()]).into_token_stream(), 85 | expr.into_token_stream() 86 | )); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /join_impl/src/handler.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! `Handler` implementation. 3 | //! 4 | 5 | use core::convert::TryFrom; 6 | use syn::{parse::ParseStream, Expr, Token}; 7 | 8 | /// 9 | /// map => expr, 10 | /// then => expr, 11 | /// and_then => expr 12 | /// 13 | pub enum Handler { 14 | Map(Expr), 15 | Then(Expr), 16 | AndThen(Expr), 17 | } 18 | 19 | mod keywords { 20 | syn::custom_keyword!(map); 21 | syn::custom_keyword!(then); 22 | syn::custom_keyword!(and_then); 23 | } 24 | 25 | impl Handler { 26 | /// 27 | /// Returns `true` if handler is `Map`. 28 | /// 29 | pub fn is_map(&self) -> bool { 30 | matches!(self, Self::Map(_)) 31 | } 32 | 33 | /// 34 | /// Returns `true` if handler is `Then`. 35 | /// 36 | pub fn is_then(&self) -> bool { 37 | matches!(self, Self::Then(_)) 38 | } 39 | 40 | /// 41 | /// Returns `true` if handler is `AndThen`. 42 | /// 43 | pub fn is_and_then(&self) -> bool { 44 | matches!(self, Self::AndThen(_)) 45 | } 46 | 47 | /// 48 | /// Returns `true` if next value in input `ParseStream` is the definition of `map` `Handler`. 49 | /// 50 | fn peek_map_handler(input: ParseStream<'_>) -> bool { 51 | input.peek(keywords::map) && input.peek2(Token![=>]) 52 | } 53 | 54 | /// 55 | /// Returns `true` if next value in input `ParseStream` is the definition of `then` `Handler`. 56 | /// 57 | fn peek_then_handler(input: ParseStream<'_>) -> bool { 58 | input.peek(keywords::then) && input.peek2(Token![=>]) 59 | } 60 | 61 | /// 62 | /// Returns `true` if next value in input `ParseStream` is the definition of `and_then` `Handler`. 63 | /// 64 | fn peek_and_then_handler(input: ParseStream<'_>) -> bool { 65 | input.peek(keywords::and_then) && input.peek2(Token![=>]) 66 | } 67 | 68 | /// 69 | /// Returns `true` if next value in input `ParseStream` is the definition of `Handler`. 70 | /// 71 | pub fn peek_handler(input: ParseStream<'_>) -> bool { 72 | Self::peek_then_handler(input) 73 | || Self::peek_and_then_handler(input) 74 | || Self::peek_map_handler(input) 75 | } 76 | 77 | /// 78 | /// Extracts inner expr. 79 | /// 80 | pub fn extract_expr(&self) -> &Expr { 81 | match self { 82 | Self::Map(expr) | Self::Then(expr) | Self::AndThen(expr) => expr, 83 | } 84 | } 85 | } 86 | 87 | impl<'a> TryFrom> for Handler { 88 | type Error = syn::Error; 89 | 90 | /// 91 | /// Attempts to parse input as a handler. 92 | /// 93 | fn try_from(input: ParseStream<'a>) -> syn::Result { 94 | let res = if Self::peek_then_handler(input) { 95 | input.parse::()?; 96 | input.parse::]>()?; 97 | Self::Then(input.parse()?) 98 | } else if Self::peek_and_then_handler(input) { 99 | input.parse::()?; 100 | input.parse::]>()?; 101 | Self::AndThen(input.parse()?) 102 | } else if Self::peek_map_handler(input) { 103 | input.parse::()?; 104 | input.parse::]>()?; 105 | Self::Map(input.parse()?) 106 | } else { 107 | return Err(syn::Error::new(input.span(), "Failed to parse `Handler`")); 108 | }; 109 | input.parse::>()?; 110 | 111 | Ok(res) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /join_impl/src/chain/group/expr_group.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Definition of `ExprGroup` . 3 | //! 4 | use crate::chain::expr::{ActionExpr, InnerExpr}; 5 | use syn::Expr; 6 | 7 | use super::*; 8 | 9 | /// 10 | /// `Action` with `Expr` of type either `Process`, `Initial` or `Err`. 11 | /// 12 | #[derive(Clone, PartialEq, Eq, Debug)] 13 | pub struct ExprGroup { 14 | expr: E, 15 | action: ActionGroup, 16 | } 17 | 18 | impl ExprGroup { 19 | /// 20 | /// Creates new `ExprGroup` with given expr and config. 21 | /// 22 | pub fn new(expr: ActionExpr, action: ActionGroup) -> Self { 23 | Self { expr, action } 24 | } 25 | 26 | /// 27 | /// Returns inner expression. 28 | /// 29 | pub fn expr(&self) -> &ActionExpr { 30 | &self.expr 31 | } 32 | 33 | /// 34 | /// Returns `MoveType` of inner expr. 35 | /// 36 | pub fn move_type(&self) -> &MoveType { 37 | &self.action.move_type 38 | } 39 | 40 | /// 41 | /// Returns `ApplicationType` of inner expr. 42 | /// 43 | pub fn application_type(&self) -> &ApplicationType { 44 | &self.action.application_type 45 | } 46 | 47 | crate::parse_n_or_empty_unit_fn! { 48 | parse_empty_unit => [0, true], 49 | parse_single_unit => [1, false], 50 | parse_single_or_empty_unit => [1, true], 51 | parse_double_unit => [2, false], 52 | parse_double_or_empty_unit => [2, true], 53 | parse_triple_unit => [3, false], 54 | parse_triple_or_empty_unit => [3, true], 55 | parse_quatro_unit => [4, false], 56 | parse_quatro_or_empty_unit => [4, true] 57 | } 58 | } 59 | 60 | impl InnerExpr for ExprGroup { 61 | fn replace_inner_exprs(mut self, exprs: &[Expr]) -> Option { 62 | self.expr = self.expr.replace_inner_exprs(exprs)?; 63 | Some(self) 64 | } 65 | 66 | fn inner_exprs(&self) -> Option<&[Expr]> { 67 | self.expr.inner_exprs() 68 | } 69 | 70 | fn is_replaceable(&self) -> bool { 71 | self.expr.is_replaceable() 72 | } 73 | } 74 | 75 | #[cfg(test)] 76 | mod tests { 77 | use super::*; 78 | use crate::chain::{expr::*, group::Combinator}; 79 | use syn::{parse_quote, Expr}; 80 | 81 | #[test] 82 | fn it_tests_inner_expr_trait_impl_for_action() { 83 | let expr: Expr = parse_quote! { |v| v + 1 }; 84 | let replace_expr: Expr = parse_quote! { |v| v + 2 }; 85 | 86 | for action_expr in vec![ 87 | ExprGroup::new( 88 | ActionExpr::Process(ProcessExpr::Then([expr.clone()])), 89 | ActionGroup::new(Combinator::Then, ApplicationType::Instant, MoveType::None), 90 | ), 91 | ExprGroup::new( 92 | ActionExpr::Err(ErrExpr::Or([expr.clone()])), 93 | ActionGroup::new(Combinator::Or, ApplicationType::Instant, MoveType::None), 94 | ), 95 | ExprGroup::new( 96 | ActionExpr::Initial(InitialExpr::Single([expr.clone()])), 97 | ActionGroup::new( 98 | Combinator::Initial, 99 | ApplicationType::Instant, 100 | MoveType::None, 101 | ), 102 | ), 103 | ] 104 | .into_iter() 105 | { 106 | assert_eq!(action_expr.inner_exprs().clone(), Some(&[expr.clone()][..])); 107 | assert_eq!( 108 | action_expr 109 | .replace_inner_exprs(&[replace_expr.clone()][..]) 110 | .unwrap() 111 | .inner_exprs(), 112 | Some(&[replace_expr.clone()][..]) 113 | ) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /join_impl/src/join/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Macro output generator. 3 | //! 4 | 5 | pub mod config; 6 | pub mod join_output; 7 | pub mod name_constructors; 8 | pub mod parse; 9 | 10 | pub use config::Config; 11 | 12 | use proc_macro2::TokenStream; 13 | use quote::ToTokens; 14 | use syn::Path; 15 | 16 | use super::handler::Handler; 17 | use crate::{action_expr_chain::ActionExprChain, chain::Chain}; 18 | use join_output::JoinOutput; 19 | use syn::parse_quote; 20 | 21 | /// 22 | /// Result of parsing `join!` macro input in trait form. 23 | /// 24 | pub trait JoinInput { 25 | /// 26 | /// Object with implementation of `Chain` trait used to generate macro output. 27 | /// 28 | type Chain: Chain; 29 | 30 | /// 31 | /// Optional `join!` macro handler. 32 | /// 33 | type Handler; 34 | 35 | /// 36 | /// Returns custom futures_crate_path taken from macro input if exists. 37 | /// 38 | fn futures_crate_path(&self) -> Option<&Path>; 39 | 40 | /// 41 | /// Returns branches, each of branches is an object implemented `Chain` trait. 42 | /// 43 | fn branches(&self) -> &[Self::Chain]; 44 | 45 | /// 46 | /// Returns `Handler` if exists. 47 | /// 48 | fn handler(&self) -> Option<&Self::Handler>; 49 | 50 | /// 51 | /// Returns custom joiner if exists. 52 | /// 53 | fn joiner(&self) -> Option<&TokenStream>; 54 | 55 | /// 56 | /// Returns transpose results configuration if specified. 57 | /// 58 | fn transpose_results_option(&self) -> Option; 59 | 60 | /// 61 | /// Returns lazy branches configuration if provided. 62 | /// 63 | fn lazy_branches_option(&self) -> Option; 64 | } 65 | 66 | /// 67 | /// Default struct which represents result of parsing `join!` macro input. 68 | /// 69 | pub struct JoinInputDefault { 70 | pub futures_crate_path: Option, 71 | pub custom_joiner: Option, 72 | pub transpose_results: Option, 73 | pub lazy_branches: Option, 74 | pub branches: Vec, 75 | pub handler: Option, 76 | } 77 | 78 | impl JoinInput for JoinInputDefault { 79 | type Chain = ActionExprChain; 80 | type Handler = Handler; 81 | 82 | fn futures_crate_path(&self) -> Option<&Path> { 83 | self.futures_crate_path.as_ref() 84 | } 85 | 86 | fn branches(&self) -> &[Self::Chain] { 87 | &self.branches 88 | } 89 | 90 | fn handler(&self) -> Option<&Self::Handler> { 91 | self.handler.as_ref() 92 | } 93 | 94 | fn joiner(&self) -> Option<&TokenStream> { 95 | self.custom_joiner.as_ref() 96 | } 97 | 98 | fn transpose_results_option(&self) -> Option { 99 | self.transpose_results.as_ref().copied() 100 | } 101 | 102 | fn lazy_branches_option(&self) -> Option { 103 | self.lazy_branches.as_ref().copied() 104 | } 105 | } 106 | 107 | /// 108 | /// Generates output of the `join!` macro based on parsed input and given config. 109 | /// 110 | pub fn generate_join>( 111 | join: &T, 112 | config: Config, 113 | ) -> TokenStream { 114 | let default_futures_crate_path = parse_quote! { ::futures }; 115 | 116 | JoinOutput::new( 117 | join.branches(), 118 | join.handler(), 119 | if let Some(futures_crate_path) = join.futures_crate_path() { 120 | Some(futures_crate_path) 121 | } else if config.is_async { 122 | Some(&default_futures_crate_path) 123 | } else { 124 | None 125 | }, 126 | join.joiner(), 127 | join.transpose_results_option(), 128 | join.lazy_branches_option(), 129 | config, 130 | ) 131 | .unwrap() 132 | .into_token_stream() 133 | } 134 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.3.0] 2 | 3 | Huge implementation refactoring. 4 | 5 | - `ActionExpr` is now enum of `Process`, `Err`, `Initial` 6 | - `ExprGroup` defines `ActionExpr` with `ActionGroup` 7 | - `CommandGroup` enum => `Combinator` enum 8 | - `TryFrom` implemented for `Handler` 9 | - Refactored internal macros implementation a bit 10 | - `InitialExpr` defined as enum 11 | - Remove `get_` prefix from all functions 12 | - Misc docs updates 13 | 14 | # [0.2.1] 15 | 16 | Implementation updates only 17 | 18 | - Removed `Map` and `MapOver` traits 19 | - Added special `MapParsed` trait 20 | - Trait `InnerExpr`: `extract_inner` -> `inner_exprs`, `replace_inner` -> `replace_inner_exprs` 21 | - Minor docs updates 22 | 23 | # [0.2.0] 24 | 25 | - `tokio` upgraded to `1.0.1` 26 | - `futures` upgraded to `0.3.0` 27 | - `proc_macro_hack` replaced by `proc_macro` since `proc_macro` now supports macro in expr position 28 | - Refactored implementation 29 | 30 | # [0.1.1] 31 | 32 | Misc docs fixes. 33 | 34 | # [0.1.0] 35 | 36 | Initial release. Internal implementation updates. 37 | 38 | # [0.1.0-beta.8] 39 | 40 | Fixed features implementations. Enabled all-features tests. Implementation changes. Cosmetic fixes. 41 | 42 | # [0.1.0-beta.7] 43 | 44 | Fixed incorrect docs link. 45 | 46 | # [0.1.0-beta.6] 47 | 48 | Updated Cargo.toml of `join` package. 49 | 50 | # [0.1.0-beta.5] 51 | 52 | Updated docs + internal improvements. 53 | 54 | # [0.1.0-beta.4] 55 | 56 | Added `custom_joiner`, `transpose_results`, `lazy_branches` custom configuration options. 57 | 58 | # [0.1.0-beta.3] 59 | 60 | Use combinators instead of `async` fn to spawn tokio tasks and send value. Updated docs. 61 | 62 | # [0.1.0-beta.2] 63 | 64 | Fixed docs. Restructured implementation. 65 | 66 | # [0.1.0-beta.1] 67 | 68 | Updated docs. Small refactoring. 69 | s 70 | # [0.1.0-alpha.17] 71 | 72 | Optimized steps: branches which are not active during step are excluded from code. Fixed `join_spawn!` last step behaviour. 73 | 74 | # [0.1.0-alpha.16] 75 | 76 | Handler definition moved to the beginning of generated code. `try` macros will abort execution in case of `None`/`Err` value at the end of any step. Use `::futures::try_join` in `try` `async` macros. Updated docs. Small refactoring. 77 | 78 | # [0.1.0-alpha.15] 79 | 80 | Fixed possible wrapper list. 81 | 82 | # [0.1.0-alpha.14] 83 | 84 | Corrected readme. 85 | 86 | # [0.1.0-alpha.13] 87 | 88 | Added nested combinators `>>>` and `<<<`. Chain now has `>@>` group identifier. 89 | 90 | # [0.1.0-alpha.12] 91 | 92 | Documentation fixes + small refactoring. 93 | 94 | # [0.1.0-alpha.11] 95 | 96 | Added `try_join!`, `try_join_async!`, `try_join_spawn!`, `try_join_async_spawn!`, `try_async_spawn!` which act as previously unprefixed macros. 97 | 98 | `join!`, `join_async!`, `join_spawn!`, `join_async_spawn!`, `async_spawn!` now don't have `handler` and **don't** transpose final tuple values. 99 | 100 | # [0.1.0-alpha.10] 101 | 102 | Bugfix + updated docs. Assert docs tests. 103 | 104 | # [0.1.0-alpha.9] 105 | 106 | Bug hotfix + minor docs fixes. More tests. 107 | 108 | # [0.1.0-alpha.8] 109 | 110 | Reassigned filter combinator `?>`, reassigned inspect combinator (`??`). Dot combinator now has two possible forms: (`>.`) and (`..`). Added new combinators: 111 | 112 | - Collect 113 | - Chain 114 | - FindMap 115 | - FilterMap 116 | - Enumerate 117 | - Partition 118 | - Flatten 119 | - Fold 120 | - TryFold 121 | - Find 122 | - Zip 123 | - Unzip 124 | 125 | Defined `full` and `static` features for `join_impl`. More generic implementation. `DefaultExpr` now is `ErrExpr`. Updated docs. 126 | 127 | # [0.1.0-alpha.7] 128 | 129 | Refactored `ActionExprChain`. 130 | 131 | # [0.1.0-alpha.6] 132 | 133 | `MapErr` is `DefaultExpr`, updated docs and internal macros. 134 | 135 | # [0.1.0-alpha.5] 136 | 137 | Added more tests, fixed docs + added travis. 138 | 139 | # [0.1.0-alpha.4] 140 | 141 | Wrap future into `Box::pin(...)` + fixed join_async with one branch, small documentation fixes. 142 | 143 | # [0.1.0-alpha.3] 144 | 145 | Docs hotfix. 146 | 147 | # [0.1.0-alpha.2] 148 | 149 | Added filter (`@>`). 150 | 151 | # [0.1.0-alpha.1] 152 | 153 | Initial. -------------------------------------------------------------------------------- /join_impl/src/join/name_constructors.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Name constructors for code generation by `join!` macro. 3 | //! 4 | 5 | use proc_macro2::{Ident, Span}; 6 | use quote::format_ident; 7 | 8 | /// 9 | /// Constructs name for variable with given index. 10 | /// 11 | pub fn construct_var_name(index: impl Into) -> Ident { 12 | format_ident!("__v{}", index.into()) 13 | } 14 | /// 15 | /// Constructs step result name using given index. 16 | /// 17 | pub fn construct_step_results_name(index: impl Into) -> Ident { 18 | format_ident!("__sr{}", index.into()) 19 | } 20 | 21 | /// 22 | /// Constructs result name with given index. 23 | /// 24 | pub fn construct_result_name(index: impl Into) -> Ident { 25 | format_ident!("__r{}", index.into()) 26 | } 27 | 28 | /// 29 | /// Constructs thread builder name with given index. 30 | /// 31 | pub fn construct_thread_builder_name(index: impl Into) -> Ident { 32 | format_ident!("__j{}", index.into()) 33 | } 34 | 35 | /// 36 | /// Constructs inspect function name. 37 | /// 38 | pub fn construct_inspect_fn_name() -> Ident { 39 | Ident::new("__inspect", Span::call_site()) 40 | } 41 | 42 | /// 43 | /// Constructs `tokio::spawn` wrapper function name. 44 | /// 45 | pub fn construct_spawn_tokio_fn_name() -> Ident { 46 | Ident::new("__spawn_tokio", Span::call_site()) 47 | } 48 | 49 | /// 50 | /// Constructs results name. 51 | /// 52 | pub fn construct_results_name() -> Ident { 53 | Ident::new("__rs", Span::call_site()) 54 | } 55 | 56 | /// 57 | /// Constructs handler name. 58 | /// 59 | pub fn construct_handler_name() -> Ident { 60 | Ident::new("__h", Span::call_site()) 61 | } 62 | 63 | /// 64 | /// Constructs internal value name with no index. 65 | /// 66 | pub fn construct_internal_value_name() -> Ident { 67 | Ident::new("__v", Span::call_site()) 68 | } 69 | 70 | /// 71 | /// Constructs result wrapper in order to be used when expression is block. 72 | /// 73 | pub fn construct_expr_wrapper_name( 74 | index: impl Into, 75 | expr_index: impl Into, 76 | internal_index: impl Into, 77 | ) -> Ident { 78 | format_ident!( 79 | "__ew{}_{}_{}", 80 | index.into(), 81 | expr_index.into(), 82 | internal_index.into() 83 | ) 84 | } 85 | 86 | /// 87 | /// Constructs thread builder fn name. This function will generate thread builder with specified name. 88 | /// This name will be displayed as result of thread::current().name().unwrap() or 89 | /// in case of thread's panic. 90 | /// 91 | /// # Example: 92 | /// ``` 93 | /// extern crate join; 94 | /// 95 | /// use std::thread; 96 | /// use join::join_spawn; 97 | /// 98 | /// join_spawn! { 99 | /// Ok::<_, ()>("hello world") |> |value| { 100 | /// println!("{}", thread::current().name().unwrap()); // main_join_0 101 | /// value 102 | /// } 103 | /// }; 104 | /// ``` 105 | /// In runtime thread's name will be constructed from name of parent thread and join_%branch_index%. 106 | /// 107 | /// # Example with several branches: 108 | /// ``` 109 | /// extern crate join; 110 | /// 111 | /// use std::thread; 112 | /// 113 | /// use join::try_join_spawn; 114 | /// 115 | /// fn current_thread_name() -> String { 116 | /// thread::current().name().unwrap().to_owned() 117 | /// } 118 | /// 119 | /// fn print_branch_thread_name(index: &Result) { 120 | /// println!("Branch: {}. Thread name: {}.", index.unwrap(), current_thread_name()); 121 | /// } 122 | /// 123 | /// let _ = try_join_spawn! { 124 | /// Ok(0) ?? print_branch_thread_name, 125 | /// Ok(1) ?? print_branch_thread_name, 126 | /// try_join_spawn! { 127 | /// Ok(2) ?? print_branch_thread_name, 128 | /// try_join_spawn! { 129 | /// Ok(3) ?? print_branch_thread_name, 130 | /// } 131 | /// } 132 | /// }.unwrap(); 133 | /// 134 | /// // Branch: 0. Thread name: main_join_0. 135 | /// // Branch: 1. Thread name: main_join_1. 136 | /// // Branch: 2. Thread name: main_join_2_join_0. 137 | /// // Branch: 3. Thread name: main_join_2_join_1_join_0. 138 | /// // Order could be different. 139 | /// ``` 140 | /// 141 | pub fn construct_thread_builder_fn_name() -> Ident { 142 | Ident::new("__tb", Span::call_site()) 143 | } 144 | -------------------------------------------------------------------------------- /join_impl/src/chain/expr/err_expr.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Definition of `ErrExpr`. 3 | //! 4 | use proc_macro2::TokenStream; 5 | use quote::{quote, ToTokens}; 6 | use syn::Expr; 7 | 8 | use super::{ActionExpr, InnerExpr}; 9 | 10 | /// 11 | /// Defines types of expression which will be evaluated in case of `Err` or `None`. 12 | /// 13 | #[derive(Clone, PartialEq, Eq, Debug)] 14 | pub enum ErrExpr { 15 | /// 16 | /// .or(Expr) 17 | /// 18 | Or([Expr; 1]), 19 | /// 20 | /// .or_else(Expr) 21 | /// 22 | OrElse([Expr; 1]), 23 | /// 24 | /// .map_err(Expr) 25 | /// 26 | MapErr([Expr; 1]), 27 | } 28 | 29 | impl ToTokens for ErrExpr { 30 | fn to_tokens(&self, output: &mut TokenStream) { 31 | let tokens = match self { 32 | Self::Or([expr]) => { 33 | quote! { .or(#expr) } 34 | } 35 | Self::OrElse([expr]) => { 36 | quote! { .or_else(#expr) } 37 | } 38 | Self::MapErr([expr]) => { 39 | quote! { .map_err(#expr) } 40 | } 41 | }; 42 | output.extend(tokens); 43 | } 44 | } 45 | 46 | impl InnerExpr for ErrExpr { 47 | fn inner_exprs(&self) -> Option<&[Expr]> { 48 | Some(match self { 49 | Self::Or(expr) => expr, 50 | Self::OrElse(expr) => expr, 51 | Self::MapErr(expr) => expr, 52 | }) 53 | } 54 | 55 | fn replace_inner_exprs(self, exprs: &[Expr]) -> Option { 56 | exprs.last().cloned().map(|expr| match self { 57 | Self::Or(_) => Self::Or([expr]), 58 | Self::OrElse(_) => Self::OrElse([expr]), 59 | Self::MapErr(_) => Self::MapErr([expr]), 60 | }) 61 | } 62 | } 63 | 64 | impl From for ActionExpr { 65 | fn from(val: ErrExpr) -> Self { 66 | ActionExpr::Err(val) 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::*; 73 | use syn::{parse_quote, Expr}; 74 | 75 | fn are_streams_equal(a: TokenStream, b: TokenStream) -> bool { 76 | format!("{:#?}", a) == format!("{:#?}", b) 77 | } 78 | 79 | #[test] 80 | fn it_tests_inner_expr_trait_impl_for_err_expr() { 81 | let expr: Expr = parse_quote! { |v| v + 1 }; 82 | 83 | for err_expr in vec![ 84 | ErrExpr::Or([expr.clone()]), 85 | ErrExpr::OrElse([expr.clone()]), 86 | ErrExpr::MapErr([expr.clone()]), 87 | ] 88 | .into_iter() 89 | { 90 | assert_eq!(err_expr.inner_exprs().clone(), Some(&[expr.clone()][..])); 91 | } 92 | } 93 | 94 | #[test] 95 | fn it_tests_inner_expr_trait_impl_replace_inner_for_err_expr() { 96 | let expr: Expr = parse_quote! { |v| v + 1 }; 97 | let replace_inner: Expr = parse_quote! { |v| 1 + v }; 98 | 99 | for err_expr in vec![ 100 | ErrExpr::Or([expr.clone()]), 101 | ErrExpr::OrElse([expr.clone()]), 102 | ErrExpr::MapErr([expr]), 103 | ] 104 | .into_iter() 105 | { 106 | assert_eq!( 107 | err_expr 108 | .replace_inner_exprs(&[replace_inner.clone()][..]) 109 | .unwrap() 110 | .inner_exprs() 111 | .clone(), 112 | Some(&[replace_inner.clone()][..]) 113 | ); 114 | } 115 | } 116 | 117 | #[test] 118 | fn it_tests_to_tokens_trait_impl_for_err_expr() { 119 | let expr: Expr = parse_quote! { |v| v + 1 }; 120 | 121 | for err_expr in vec![ 122 | ErrExpr::Or([expr.clone()]), 123 | ErrExpr::OrElse([expr.clone()]), 124 | ErrExpr::MapErr([expr]), 125 | ] 126 | .into_iter() 127 | { 128 | let mut token_stream = TokenStream::new(); 129 | err_expr.to_tokens(&mut token_stream); 130 | assert!(are_streams_equal( 131 | token_stream.clone(), 132 | err_expr.clone().into_token_stream() 133 | )); 134 | assert!(are_streams_equal( 135 | token_stream, 136 | match err_expr { 137 | ErrExpr::Or([expr]) => { 138 | quote! { .or(#expr) } 139 | } 140 | ErrExpr::OrElse([expr]) => { 141 | quote! { .or_else(#expr) } 142 | } 143 | ErrExpr::MapErr([expr]) => { 144 | quote! { .map_err(#expr) } 145 | } 146 | } 147 | )) 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /join_impl/src/parse/utils.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Utils for `join!` macro parsing and code generation. 3 | //! 4 | 5 | use core::fmt::Debug; 6 | use proc_macro2::{TokenStream, TokenTree}; 7 | use quote::ToTokens; 8 | use syn::{ 9 | parse::{Parse, ParseStream}, 10 | parse2, Expr, 11 | }; 12 | 13 | use super::unit::{Unit, UnitResult}; 14 | use crate::chain::group::{ActionGroup, ApplicationType, Combinator, GroupDeterminer, MoveType}; 15 | 16 | /// 17 | /// Returns `true` if given stream is valid `Expr`. 18 | /// 19 | pub fn is_valid_expr(input: TokenStream) -> bool { 20 | is_valid_stream::(input) 21 | } 22 | 23 | /// 24 | /// Returns `true` if given stream can be parsed as `T`. 25 | /// 26 | pub fn is_valid_stream(input: TokenStream) -> bool { 27 | syn::parse2::(input).is_ok() 28 | } 29 | 30 | /// 31 | /// Returns `true` if expr is {...}. 32 | /// 33 | pub fn is_block_expr(expr: &Expr) -> bool { 34 | matches!(expr, Expr::Block(_)) 35 | } 36 | 37 | /// 38 | /// Parses input `ParseStream` until one of provided `GroupDeterminer`'s check will be valid or it reaches end. 39 | /// 40 | pub fn parse_until<'a, T: Parse + Clone + Debug>( 41 | input: ParseStream<'_>, 42 | group_determiners: impl Iterator + Clone, 43 | deferred_determiner: &'a GroupDeterminer, 44 | wrapper_determiner: &'a GroupDeterminer, 45 | allow_empty_parsed: bool, 46 | ) -> UnitResult { 47 | let group_count = group_determiners.clone().count(); 48 | let mut group_determiners = group_determiners.cycle(); 49 | 50 | let mut tokens = TokenStream::new(); 51 | let mut next = None; 52 | let mut deferred = false; 53 | let mut wrap = false; 54 | 55 | while !input.is_empty() 56 | && !{ 57 | let group_determiners = &mut group_determiners; 58 | 59 | deferred = deferred_determiner.check_input(input); 60 | if deferred { 61 | deferred_determiner.erase_input(input)?; 62 | } 63 | 64 | let possible_group = group_determiners 65 | .take(group_count) 66 | .find(|group| group.check_input(input)); 67 | possible_group 68 | .map(|group| { 69 | tokens.is_empty() && allow_empty_parsed 70 | || group.check_parsed::(tokens.clone()) 71 | }) 72 | .unwrap_or(false) 73 | && { 74 | next = possible_group; 75 | true 76 | } 77 | } 78 | { 79 | let next: TokenTree = input.parse()?; 80 | next.to_tokens(&mut tokens); 81 | } 82 | 83 | // 84 | // Parses group determiner's tokens. (for ex. => -> |> etc.) 85 | // 86 | if let Some(group) = next { 87 | if let Some(combinator) = group.combinator() { 88 | let forked = input.fork(); 89 | group.erase_input(&forked)?; 90 | 91 | wrap = wrapper_determiner.check_input(&forked); 92 | if wrap && combinator == Combinator::UNWRAP { 93 | return Err(input.error("Action can be either wrapped or unwrapped but not both")); 94 | } else if wrap && !combinator.can_be_wrapper() { 95 | return Err(input.error("This combinator can't be wrapper")); 96 | } 97 | if wrap { 98 | wrapper_determiner.erase_input(input)?; 99 | } 100 | } 101 | group.erase_input(input)?; 102 | } 103 | 104 | Ok(Unit { 105 | parsed: parse2(tokens)?, 106 | next: next.and_then(|group| { 107 | group.combinator().map(|combinator| { 108 | ActionGroup::new( 109 | combinator, 110 | if deferred { 111 | ApplicationType::Deferred 112 | } else { 113 | ApplicationType::Instant 114 | }, 115 | if wrap { 116 | MoveType::Wrap 117 | } else if combinator == Combinator::UNWRAP { 118 | MoveType::Unwrap 119 | } else { 120 | MoveType::None 121 | }, 122 | ) 123 | }) 124 | }), 125 | }) 126 | } 127 | 128 | /// 129 | /// Skips next item in `ParseStream`. Returns `true` in case of success. 130 | /// 131 | pub fn skip(input: ParseStream<'_>) -> bool { 132 | input 133 | .step(|cursor| { 134 | if let Some((_lifetime, rest)) = cursor.lifetime() { 135 | Ok((true, rest)) 136 | } else if let Some((_token, rest)) = cursor.token_tree() { 137 | Ok((true, rest)) 138 | } else { 139 | Ok((false, *cursor)) 140 | } 141 | }) 142 | .unwrap() 143 | } 144 | -------------------------------------------------------------------------------- /join_impl/src/join/parse.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Parser which takes expression chains and puts them into `branches` field, 3 | //! and handler (one of `map`, `and_then`, `then`) and puts it into `handler` field. 4 | //! Handler can be either defined once or not defined. 5 | //! 6 | //! 7 | use super::JoinInputDefault; 8 | use crate::{ 9 | action_expr_chain::ActionExprChainBuilder, 10 | chain::{group::GroupDeterminer, parse_chain::ParseChain}, 11 | handler::Handler, 12 | }; 13 | use alloc::vec::Vec; 14 | use core::convert::TryFrom; 15 | use syn::{ 16 | parenthesized, 17 | parse::{Parse, ParseStream}, 18 | LitBool, Token, 19 | }; 20 | 21 | mod keywords { 22 | syn::custom_keyword!(futures_crate_path); 23 | syn::custom_keyword!(transpose_results); 24 | syn::custom_keyword!(lazy_branches); 25 | syn::custom_keyword!(custom_joiner); 26 | syn::custom_keyword!(n); 27 | } 28 | 29 | /// 30 | /// Default `GroupDeterminer`'s definitions. 31 | /// 32 | pub const DEFAULT_GROUP_DETERMINERS: &[GroupDeterminer] = &crate::define_group_determiners! { 33 | UNWRAP => Token![<], Token![<], Token![<] => 3, 34 | Collect => Token![=], Token![>], syn::token::Bracket => 3, 35 | Map => Token![|], Token![>] => 2, 36 | Then => Token![->] => 2, 37 | AndThen => Token![=>] => 2, 38 | Or => Token![<], Token![|] => 2, 39 | OrElse => Token![<=] => 2, 40 | Dot => Token![>], Token![.] => 2, 41 | Dot => Token![..] => 2, 42 | MapErr => Token![!], Token![>] => 2, 43 | Chain => Token![>], Token![@], Token![>] => 3, 44 | Inspect => Token![?], Token![?] => 2, 45 | Filter => Token![?], Token![>] => 2, 46 | FindMap => Token![?], Token![|], Token![>], Token![@] => 4, 47 | FilterMap => Token![?], Token![|], Token![>] => 3, 48 | Enumerate => Token![|], keywords::n, Token![>] => 3, 49 | Partition => Token![?], Token![&], Token![!], Token![>] => 4, 50 | Flatten => Token![^], Token![^], Token![>] => 3, 51 | Fold => Token![^], Token![@] => 2, 52 | TryFold => Token![?], Token![^], Token![@] => 3, 53 | Find => Token![?], Token![@] => 2, 54 | Zip => Token![>], Token![^], Token![>] => 3, 55 | Unzip => Token![<], Token![-], Token![>] => 3 56 | }; 57 | 58 | /// 59 | /// `GroupDeterminer` used to determine deferred group. 60 | /// 61 | pub const DEFERRED_DETERMINER: &GroupDeterminer = &crate::define_determiner_with_no_group! { 62 | Token![~] => 1 63 | }; 64 | 65 | /// 66 | /// `GroupDeterminer` used to determine wrapper group. 67 | /// 68 | pub const WRAPPER_DETERMINER: &GroupDeterminer = &crate::define_determiner_with_no_group! { 69 | Token![>], Token![>], Token![>] => 3 70 | }; 71 | 72 | impl Parse for JoinInputDefault { 73 | fn parse(input: ParseStream<'_>) -> syn::Result { 74 | let mut join = Self { 75 | branches: Vec::new(), 76 | handler: None, 77 | futures_crate_path: None, 78 | custom_joiner: None, 79 | transpose_results: None, 80 | lazy_branches: None, 81 | }; 82 | 83 | let action_expr_chain_builder = ActionExprChainBuilder::new( 84 | DEFAULT_GROUP_DETERMINERS, 85 | DEFERRED_DETERMINER, 86 | WRAPPER_DETERMINER, 87 | ); 88 | 89 | for _ in 0..4 { 90 | if input.peek(keywords::futures_crate_path) { 91 | input.parse::()?; 92 | let content; 93 | parenthesized!(content in input); 94 | if join.futures_crate_path.is_some() { 95 | return Err(input.error("futures_crate_path specified twice")); 96 | } 97 | join.futures_crate_path = Some(content.parse()?); 98 | } 99 | 100 | if input.peek(keywords::custom_joiner) { 101 | input.parse::()?; 102 | let content; 103 | parenthesized!(content in input); 104 | if join.custom_joiner.is_some() { 105 | return Err(input.error("custom_joiner specified twice")); 106 | } 107 | join.custom_joiner = Some(content.parse()?); 108 | } 109 | 110 | if input.peek(keywords::transpose_results) { 111 | input.parse::()?; 112 | let content; 113 | parenthesized!(content in input); 114 | if join.transpose_results.is_some() { 115 | return Err(input.error("transpose_results specified twice")); 116 | } 117 | join.transpose_results = Some(content.parse::()?.value); 118 | } 119 | 120 | if input.peek(keywords::lazy_branches) { 121 | input.parse::()?; 122 | let content; 123 | parenthesized!(content in input); 124 | if join.lazy_branches.is_some() { 125 | return Err(input.error("lazy_branches specified twice")); 126 | } 127 | join.lazy_branches = Some(content.parse::()?.value); 128 | } 129 | } 130 | 131 | while !input.is_empty() { 132 | if Handler::peek_handler(input) { 133 | if join.handler.is_some() { 134 | return Err(input.error("Multiple `handler` cases found, only one allowed. Please, specify one of `map`, `and_then`, `then`.")); 135 | } 136 | join.handler = Some(Handler::try_from(input)?); 137 | } else { 138 | let expr_chain = action_expr_chain_builder.build_from_parse_stream(input)?; 139 | join.branches.push(expr_chain); 140 | }; 141 | } 142 | 143 | if join.branches.is_empty() { 144 | Err(input.error("join must contain at least 1 branch.")) 145 | } else { 146 | Ok(join) 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /join_impl/src/chain/expr/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! parse_n_or_empty_unit_fn_signature { 3 | (0, true, $fn_name: ident) => { 4 | pub fn $fn_name< 5 | ResultExpr: Into<$crate::chain::expr::ActionExpr> + Clone + std::fmt::Debug, 6 | >( 7 | result_expr: ResultExpr, 8 | unit_parser: &impl $crate::parse::unit::ParseUnit<$crate::chain::group::ActionGroup>, 9 | action_group: &$crate::chain::group::ActionGroup, 10 | input: $crate::syn::parse::ParseStream<'_>, 11 | ) -> $crate::parse::unit::UnitResult< 12 | $crate::chain::group::ExprGroup<$crate::chain::expr::ActionExpr>, 13 | $crate::chain::group::ActionGroup, 14 | > { 15 | $crate::parse_n_or_empty_unit_fn_body!( 16 | 0, 17 | true, 18 | $crate::parse::Empty, 19 | ResultExpr, 20 | |_| result_expr, 21 | unit_parser, 22 | action_group, 23 | input 24 | ) 25 | } 26 | }; 27 | ($unit_count: tt, false, $fn_name: ident) => { 28 | pub fn $fn_name< 29 | P: $crate::syn::parse::Parse + Clone + std::fmt::Debug, 30 | ResultExpr: Into<$crate::chain::expr::ActionExpr> + Clone + std::fmt::Debug, 31 | >( 32 | into_expr: impl FnOnce([P; $unit_count]) -> ResultExpr, 33 | unit_parser: &impl $crate::parse::unit::ParseUnit<$crate::chain::group::ActionGroup>, 34 | action_group: &$crate::chain::group::ActionGroup, 35 | input: $crate::syn::parse::ParseStream<'_>, 36 | ) -> $crate::parse::unit::UnitResult< 37 | $crate::chain::group::ExprGroup<$crate::chain::expr::ActionExpr>, 38 | $crate::chain::group::ActionGroup, 39 | > { 40 | use std::convert::TryInto; 41 | $crate::parse_n_or_empty_unit_fn_body!( 42 | $unit_count, 43 | false, 44 | P, 45 | ResultExpr, 46 | |val: Option>| into_expr( 47 | val.and_then(|v| v.try_into().ok()) 48 | .expect("join: Invalid parsed unit count. This's a bug, please report it.") 49 | ), 50 | unit_parser, 51 | action_group, 52 | input 53 | ) 54 | } 55 | }; 56 | ($unit_count: tt, true, $fn_name: ident) => { 57 | pub fn $fn_name< 58 | P: $crate::syn::parse::Parse + Clone + std::fmt::Debug, 59 | ResultExpr: Into<$crate::chain::expr::ActionExpr> + Clone + std::fmt::Debug, 60 | >( 61 | into_expr: impl FnOnce(Option<[P; $unit_count]>) -> ResultExpr, 62 | unit_parser: &impl $crate::parse::unit::ParseUnit<$crate::chain::group::ActionGroup>, 63 | action_group: &$crate::chain::group::ActionGroup, 64 | input: $crate::syn::parse::ParseStream<'_>, 65 | ) -> $crate::parse::unit::UnitResult< 66 | $crate::chain::group::ExprGroup<$crate::chain::expr::ActionExpr>, 67 | $crate::chain::group::ActionGroup, 68 | > { 69 | use std::convert::TryInto; 70 | $crate::parse_n_or_empty_unit_fn_body!( 71 | $unit_count, 72 | true, 73 | P, 74 | ResultExpr, 75 | |val: Option>| into_expr(val.and_then(|v| v.try_into().ok())), 76 | unit_parser, 77 | action_group, 78 | input 79 | ) 80 | } 81 | }; 82 | } 83 | 84 | #[macro_export] 85 | macro_rules! parse_n_or_empty_unit_fn_body { 86 | ($unit_count: tt, $allow_empty: tt, $parse_type: path, $out_type: path, $into_expr: expr, $unit_parser: expr, $action_group: expr, $input: expr) => {{ 87 | use $crate::syn::Token; 88 | use $crate::parse::Empty; 89 | use $crate::chain::group::ExprGroup; 90 | use $crate::parse::unit::{MapParsed, Unit}; 91 | 92 | fn to_none(_: T) -> Option { 93 | None 94 | } 95 | 96 | let unit_count = $unit_count; 97 | let into_expr = $into_expr; 98 | let unit_parser = $unit_parser; 99 | let action_group = $action_group; 100 | let input = $input; 101 | if $allow_empty { 102 | unit_parser 103 | .parse_unit::(&input.fork(), true) 104 | .and_then(|_| unit_parser.parse_unit::(&input, true)) 105 | .map_parsed(to_none::<_, Vec<$parse_type>>) 106 | } else { 107 | Err::>, $crate::chain::group::ActionGroup>, _>($crate::syn::Error::new(input.span(), "Can't parse empty unit")) 108 | }.or_else(|_| { 109 | (0..unit_count) 110 | .map(|index| { 111 | unit_parser 112 | .parse_unit::<$parse_type>(input, false) 113 | .and_then(|unit| { 114 | if index + 1 < unit_count { 115 | input.parse::().and_then(|_| { 116 | if unit.next.is_none() { 117 | Ok(unit) 118 | } else { 119 | Err(input.error(&format!( 120 | "Expected {} units, found group identifier!", 121 | unit_count 122 | ))) 123 | } 124 | }) 125 | } else { 126 | Ok(unit) 127 | } 128 | }) 129 | }) 130 | .try_fold( 131 | Unit { 132 | parsed: Some(Vec::with_capacity(unit_count)), 133 | next: None, 134 | }, 135 | |mut acc, unit| { 136 | unit.map(|unit| { 137 | acc.parsed.as_mut().unwrap().push(unit.parsed); 138 | acc.next = unit.next; 139 | acc 140 | }) 141 | }, 142 | ) 143 | }) 144 | .map_parsed(into_expr) 145 | .map_parsed(|parsed| { 146 | ExprGroup::new( 147 | parsed.into(), 148 | *action_group 149 | ) 150 | }) 151 | }}} 152 | 153 | #[macro_export] 154 | macro_rules! parse_n_or_empty_unit_fn { 155 | ($($fn_name: ident => [$unit_count: tt, $allow_empty: tt]),+) => {$( 156 | $crate::parse_n_or_empty_unit_fn_signature!($unit_count, $allow_empty, $fn_name); 157 | )+}; 158 | } 159 | -------------------------------------------------------------------------------- /join_impl/src/chain/group/group_determiner.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Definition of `GroupDeterminer` and related macros. 3 | //! 4 | 5 | use crate::parse::utils::is_valid_stream; 6 | use proc_macro2::{TokenStream, TokenTree}; 7 | use syn::parse::{Parse, ParseStream}; 8 | 9 | use super::Combinator; 10 | 11 | pub type CheckStreamFn = fn(ParseStream) -> bool; 12 | 13 | #[derive(Clone, Copy)] 14 | union CheckStreamFnPointer { 15 | f: CheckStreamFn, 16 | raw: *const (), 17 | } 18 | 19 | unsafe impl std::marker::Send for CheckStreamFnPointer {} 20 | 21 | unsafe impl std::marker::Sync for CheckStreamFnPointer {} 22 | 23 | /// 24 | /// `GroupDeterminer` is used to determine any `Combinator` or separator (for ex. `,`) in `ParseStream` 25 | /// 26 | #[derive(Clone)] 27 | pub struct GroupDeterminer { 28 | combinator: Option, 29 | check_input_fn: CheckStreamFnPointer, 30 | validate_parsed: bool, 31 | length: u8, 32 | } 33 | 34 | /// 35 | /// Creates `GroupDeterminer` for given `Combinator`s with provided tokens. 36 | /// 37 | #[macro_export] 38 | macro_rules! define_group_determiners { 39 | ($($combinator: ident => $($token: expr),+ => $length: expr),+) => {{ 40 | [ 41 | $crate::define_determiner_with_no_group!(Token![,] => 0), 42 | $( 43 | $crate::define_group_determiner!( 44 | $crate::chain::group::Combinator::$combinator => $($token),+ => $length 45 | ) 46 | ),*, 47 | $crate::chain::group::GroupDeterminer::new_const( 48 | None, 49 | $crate::handler::Handler::peek_handler as *const (), 50 | true, 51 | 0 52 | ) 53 | ] 54 | }}; 55 | } 56 | 57 | /// 58 | /// Creates `GroupDeterminer` with no `Combinator` and provided tokens. 59 | /// 60 | #[macro_export] 61 | macro_rules! define_determiner_with_no_group { 62 | ($($token: expr),+ => $length: expr) => {{ 63 | let check_tokens = $crate::define_tokens_checker!($($token),+); 64 | $crate::chain::group::GroupDeterminer::new_const( 65 | None, 66 | check_tokens as *const (), 67 | true, 68 | $length 69 | ) 70 | }} 71 | } 72 | 73 | /// 74 | /// Creates function which checks if `ParseStream` next values are provided tokens. 75 | /// 76 | #[macro_export] 77 | macro_rules! define_tokens_checker { 78 | ($token1: expr, $token2:expr, $token3:expr) => {{ 79 | fn check_tokens(input: ::syn::parse::ParseStream<'_>) -> bool { 80 | input.peek($token1) && input.peek2($token2) && input.peek3($token3) 81 | } 82 | check_tokens 83 | }}; 84 | ($token1: expr, $token2:expr) => {{ 85 | fn check_tokens(input: ::syn::parse::ParseStream<'_>) -> bool { 86 | input.peek($token1) && input.peek2($token2) 87 | } 88 | check_tokens 89 | }}; 90 | ($token: expr) => {{ 91 | fn check_tokens(input: ::syn::parse::ParseStream<'_>) -> bool { input.peek($token) } 92 | check_tokens 93 | }}; 94 | ($($token: expr),+) => {{ 95 | fn check_tokens(input: ::syn::parse::ParseStream<'_>) -> bool { 96 | let input = input.fork(); 97 | $( 98 | input.peek($token) && $crate::parse::utils::skip(&input) 99 | )&&+ 100 | } 101 | check_tokens 102 | }}; 103 | } 104 | 105 | /// 106 | /// Creates `GroupDeterminer` with given (`Combinator` => tokens => length => ?Check parsed tokens? (optional bool)) 107 | /// 108 | /// # Example: 109 | /// ``` 110 | /// use join_impl::chain::group::Combinator; 111 | /// use join_impl::define_group_determiner; 112 | /// use syn::Token; 113 | /// 114 | /// let then_determiner = define_group_determiner!(Combinator::Then => Token![->] => 2); // last param is optional, `true` by default 115 | /// ``` 116 | /// 117 | #[macro_export] 118 | macro_rules! define_group_determiner { 119 | ($combinator: expr => $($tokens: expr),+ => $length: expr => $validate_parsed: expr) => {{ 120 | let check_tokens = $crate::define_tokens_checker!($($tokens),*); 121 | $crate::chain::group::GroupDeterminer::new_const( 122 | Some($combinator), 123 | check_tokens as *const (), 124 | $validate_parsed, 125 | $length 126 | ) 127 | }}; 128 | ($combinator: expr => $($tokens: expr),+ => $length: expr) => { 129 | $crate::define_group_determiner!( 130 | $combinator => $($tokens),+ => $length => true 131 | ) 132 | }; 133 | } 134 | 135 | impl GroupDeterminer { 136 | /// 137 | /// Constructs new `GroupDeterminer` using `const fn` (can be used to create const or static item). 138 | /// 139 | /// # Example: 140 | /// ``` 141 | /// extern crate join_impl; 142 | /// extern crate syn; 143 | /// 144 | /// use syn::Token; 145 | /// use syn::parse::ParseStream; 146 | /// use join_impl::chain::group::GroupDeterminer; 147 | /// 148 | /// fn check_input(input: ParseStream) -> bool { input.peek(Token![,]) } 149 | /// 150 | /// let first_comma_determiner = GroupDeterminer::new_const( 151 | /// None, // Because comma is not a command group 152 | /// check_input as *const (), 153 | /// false, 154 | /// 1 155 | /// ); 156 | /// ``` 157 | /// 158 | pub const fn new_const( 159 | combinator: Option, 160 | check_input_fn: *const (), 161 | validate_parsed: bool, 162 | length: u8, 163 | ) -> Self { 164 | Self { 165 | combinator, 166 | check_input_fn: CheckStreamFnPointer { 167 | raw: check_input_fn, 168 | }, 169 | validate_parsed, 170 | length, 171 | } 172 | } 173 | 174 | /// 175 | /// Constructs new `GroupDeterminer`. 176 | /// 177 | /// # Example: 178 | /// ``` 179 | /// extern crate join_impl; 180 | /// extern crate syn; 181 | /// 182 | /// use syn::Token; 183 | /// use syn::parse::ParseStream; 184 | /// use join_impl::chain::group::GroupDeterminer; 185 | /// 186 | /// fn check_input(input: ParseStream) -> bool { input.peek(Token![,]) } 187 | /// 188 | /// let first_comma_determiner = GroupDeterminer::new( 189 | /// None, // Because comma is not a command group 190 | /// check_input, 191 | /// false, 192 | /// 1 193 | /// ); 194 | /// ``` 195 | /// 196 | pub fn new( 197 | combinator: impl Into>, 198 | check_input_fn: CheckStreamFn, 199 | validate_parsed: bool, 200 | length: u8, 201 | ) -> Self { 202 | Self { 203 | combinator: combinator.into(), 204 | check_input_fn: CheckStreamFnPointer { f: check_input_fn }, 205 | validate_parsed, 206 | length, 207 | } 208 | } 209 | 210 | /// 211 | /// Returns type of group of `GroupDeterminer`. 212 | /// 213 | pub fn combinator(&self) -> Option { 214 | self.combinator 215 | } 216 | 217 | /// 218 | /// Checks if input next tokens are of self group type. 219 | /// 220 | pub fn check_input(&self, input: ParseStream<'_>) -> bool { 221 | (unsafe { self.check_input_fn.f })(input) 222 | } 223 | 224 | /// 225 | /// Checks already parsed tokens. In many cases it's used to check if 226 | /// parsed tokens are valid expression. in this case we can say for sure that 227 | /// we found separator. 228 | /// 229 | pub fn check_parsed(&self, input: TokenStream) -> bool { 230 | !self.validate_parsed || is_valid_stream::(input) 231 | } 232 | 233 | /// 234 | /// Used to parse `length` tokens of type `TokenTree` from input `ParseStream`. 235 | /// 236 | pub fn erase_input<'b>(&self, input: ParseStream<'b>) -> syn::Result> { 237 | for _ in 0..self.len() { 238 | input.parse::()?; 239 | } 240 | Ok(input) 241 | } 242 | 243 | /// 244 | /// Returns value of `length` field. 245 | /// 246 | pub fn len(&self) -> u8 { 247 | self.length 248 | } 249 | 250 | /// 251 | /// Returns `true` if `length` is zero. 252 | /// 253 | pub fn is_empty(&self) -> bool { 254 | self.len() == 0 255 | } 256 | } 257 | 258 | #[cfg(test)] 259 | mod tests { 260 | use super::{super::*, *}; 261 | 262 | #[test] 263 | fn it_creates_comma_determiner() { 264 | fn check_comma(input: ParseStream) -> bool { 265 | input.peek(::syn::Token![,]) 266 | } 267 | 268 | let first_comma_determiner = GroupDeterminer::new_const( 269 | None, // Because comma is not a command group 270 | check_comma as *const (), 271 | false, 272 | 1, 273 | ); 274 | 275 | assert_eq!(first_comma_determiner.combinator(), None); 276 | assert!(first_comma_determiner.check_parsed::<::syn::Expr>(::quote::quote! { , })); 277 | assert_eq!(first_comma_determiner.len(), 1); 278 | } 279 | 280 | #[test] 281 | fn it_creates_then_determiner() { 282 | fn check_then(input: ParseStream) -> bool { 283 | input.peek(::syn::Token![=>]) 284 | } 285 | 286 | let then_determiner = GroupDeterminer::new(Combinator::Then, check_then, true, 2); 287 | 288 | assert_eq!(then_determiner.combinator(), Some(Combinator::Then)); 289 | assert!(then_determiner.check_parsed::<::syn::Expr>(::quote::quote! { 23 })); 290 | assert_eq!(then_determiner.len(), 2); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /join_impl/src/chain/group/combinator.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! All possible `Combinator`s. 3 | //! 4 | 5 | /// 6 | /// `Combinator` is an enum of all possible `ProcessExpr`, `ErrExpr` and `InitialExpr` operations. 7 | /// Used to express group which was found in input `ParseStream`. 8 | /// 9 | #[cfg(not(feature = "full"))] 10 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 11 | pub enum Combinator { 12 | /// [crate::chain::expr::ProcessExpr::Map] 13 | Map, 14 | /// [crate::chain::expr::ProcessExpr::Dot] 15 | Dot, 16 | /// [crate::chain::expr::ProcessExpr::Filter] 17 | Filter, 18 | /// [crate::chain::expr::ProcessExpr::Inspect] 19 | Inspect, 20 | /// [crate::chain::expr::ProcessExpr::Then] 21 | Then, 22 | /// [crate::chain::expr::ProcessExpr::AndThen] 23 | AndThen, 24 | /// [crate::chain::expr::ErrExpr::Or] 25 | Or, 26 | /// [crate::chain::expr::ErrExpr::OrElse] 27 | OrElse, 28 | /// [crate::chain::expr::ErrExpr::MapErr] 29 | MapErr, 30 | /// [crate::chain::expr::InitialExpr] 31 | Initial, 32 | /// [crate::chain::expr::ProcessExpr::Chain] 33 | Chain, 34 | /// [crate::chain::expr::ProcessExpr::Flatten] 35 | Flatten, 36 | /// [crate::chain::expr::ProcessExpr::Collect] 37 | Collect, 38 | /// [crate::chain::expr::ProcessExpr::Enumerate] 39 | Enumerate, 40 | /// [crate::chain::expr::ProcessExpr::Find] 41 | Find, 42 | /// [crate::chain::expr::ProcessExpr::Fold] 43 | Fold, 44 | /// [crate::chain::expr::ProcessExpr::TryFold] 45 | TryFold, 46 | /// [crate::chain::expr::ProcessExpr::Unzip] 47 | Unzip, 48 | /// [crate::chain::expr::ProcessExpr::Zip] 49 | Zip, 50 | /// [crate::chain::expr::ProcessExpr::Partition] 51 | Partition, 52 | /// [crate::chain::expr::ProcessExpr::FilterMap] 53 | FilterMap, 54 | /// [crate::chain::expr::ProcessExpr::FindMap] 55 | FindMap, 56 | /// UNWRAP (Special `Combinator` used to define that next group position is by one level up - which will be `#value.and_then(#previous_expr).#next_expr` ) 57 | UNWRAP, 58 | } 59 | 60 | /// 61 | /// All possible command groups. 62 | /// 63 | #[cfg(feature = "full")] 64 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 65 | pub enum Combinator { 66 | /// [crate::chain::expr::ProcessExpr::Map] 67 | Map, 68 | /// [crate::chain::expr::ProcessExpr::Dot] 69 | Dot, 70 | /// [crate::chain::expr::ProcessExpr::Filter] 71 | Filter, 72 | /// [crate::chain::expr::ProcessExpr::Inspect] 73 | Inspect, 74 | /// [crate::chain::expr::ProcessExpr::Then] 75 | Then, 76 | /// [crate::chain::expr::ProcessExpr::AndThen] 77 | AndThen, 78 | /// [crate::chain::expr::ErrExpr::Or] 79 | Or, 80 | /// [crate::chain::expr::ErrExpr::OrElse] 81 | OrElse, 82 | /// [crate::chain::expr::ErrExpr::MapErr] 83 | MapErr, 84 | /// [crate::chain::expr::InitialExpr] 85 | Initial, 86 | /// [crate::chain::expr::ProcessExpr::All] 87 | All, 88 | /// [crate::chain::expr::ProcessExpr::Any] 89 | Any, 90 | /// [crate::chain::expr::ProcessExpr::ByRef] 91 | ByRef, 92 | /// [crate::chain::expr::ProcessExpr::Chain] 93 | Chain, 94 | /// [crate::chain::expr::ProcessExpr::Cloned] 95 | Cloned, 96 | /// [crate::chain::expr::ProcessExpr::Cmp] 97 | Cmp, 98 | /// [crate::chain::expr::ProcessExpr::Collect] 99 | Collect, 100 | /// [crate::chain::expr::ProcessExpr::Copied] 101 | Copied, 102 | /// [crate::chain::expr::ProcessExpr::Count] 103 | Count, 104 | /// [crate::chain::expr::ProcessExpr::Cycle] 105 | Cycle, 106 | /// [crate::chain::expr::ProcessExpr::Enumerate] 107 | Enumerate, 108 | /// [crate::chain::expr::ProcessExpr::Eq] 109 | Eq, 110 | /// [crate::chain::expr::ProcessExpr::FilterMap] 111 | FilterMap, 112 | /// [crate::chain::expr::ProcessExpr::Find] 113 | Find, 114 | /// [crate::chain::expr::ProcessExpr::FindMap] 115 | FindMap, 116 | /// [crate::chain::expr::ProcessExpr::FlatMap] 117 | FlatMap, 118 | /// [crate::chain::expr::ProcessExpr::Flatten] 119 | Flatten, 120 | /// [crate::chain::expr::ProcessExpr::Fold] 121 | Fold, 122 | /// [crate::chain::expr::ProcessExpr::ForEach] 123 | ForEach, 124 | /// [crate::chain::expr::ProcessExpr::Fuse] 125 | Fuse, 126 | /// [crate::chain::expr::ProcessExpr::Ge] 127 | Ge, 128 | /// [crate::chain::expr::ProcessExpr::Gt] 129 | Gt, 130 | /// [crate::chain::expr::ProcessExpr::IsSorted] 131 | IsSorted, 132 | /// [crate::chain::expr::ProcessExpr::IsSortedBy] 133 | IsSortedBy, 134 | /// [crate::chain::expr::ProcessExpr::IsSortedByKey] 135 | IsSortedByKey, 136 | /// [crate::chain::expr::ProcessExpr::Last] 137 | Last, 138 | /// [crate::chain::expr::ProcessExpr::Le] 139 | Le, 140 | /// [crate::chain::expr::ProcessExpr::Lt] 141 | Lt, 142 | /// [crate::chain::expr::ProcessExpr::Max] 143 | Max, 144 | /// [crate::chain::expr::ProcessExpr::MaxBy] 145 | MaxBy, 146 | /// [crate::chain::expr::ProcessExpr::MaxByKey] 147 | MaxByKey, 148 | /// [crate::chain::expr::ProcessExpr::Min] 149 | Min, 150 | /// [crate::chain::expr::ProcessExpr::MinBy] 151 | MinBy, 152 | /// [crate::chain::expr::ProcessExpr::MinByKey] 153 | MinByKey, 154 | /// [crate::chain::expr::ProcessExpr::Ne] 155 | Ne, 156 | /// [crate::chain::expr::ProcessExpr::Nth] 157 | Nth, 158 | /// [crate::chain::expr::ProcessExpr::PartialCmp] 159 | PartialCmp, 160 | /// [crate::chain::expr::ProcessExpr::IsPartitioned] 161 | IsPartitioned, 162 | /// [crate::chain::expr::ProcessExpr::Partition] 163 | Partition, 164 | /// [crate::chain::expr::ProcessExpr::PartitionInPlace] 165 | PartitionInPlace, 166 | /// [crate::chain::expr::ProcessExpr::Peekable] 167 | Peekable, 168 | /// [crate::chain::expr::ProcessExpr::Position] 169 | Position, 170 | /// [crate::chain::expr::ProcessExpr::Product] 171 | Product, 172 | /// [crate::chain::expr::ProcessExpr::Rev] 173 | Rev, 174 | /// [crate::chain::expr::ProcessExpr::Rposition] 175 | Rposition, 176 | /// [crate::chain::expr::ProcessExpr::Scan] 177 | Scan, 178 | /// [crate::chain::expr::ProcessExpr::SizeHint] 179 | SizeHint, 180 | /// [crate::chain::expr::ProcessExpr::Skip] 181 | Skip, 182 | /// [crate::chain::expr::ProcessExpr::SkipWhile] 183 | SkipWhile, 184 | /// [crate::chain::expr::ProcessExpr::StepBy] 185 | StepBy, 186 | /// [crate::chain::expr::ProcessExpr::Sum] 187 | Sum, 188 | /// [crate::chain::expr::ProcessExpr::Take] 189 | Take, 190 | /// [crate::chain::expr::ProcessExpr::TakeWhile] 191 | TakeWhile, 192 | /// [crate::chain::expr::ProcessExpr::TryFold] 193 | TryFold, 194 | /// [crate::chain::expr::ProcessExpr::TryForEach] 195 | TryForEach, 196 | /// UNWRAP (Special `Combinator` used to define that next group position is by one level up - which will be `#value.and_then(#previous_expr).#next_expr` ) 197 | UNWRAP, 198 | /// [crate::chain::expr::ProcessExpr::Unzip] 199 | Unzip, 200 | /// [crate::chain::expr::ProcessExpr::Zip] 201 | Zip, 202 | } 203 | 204 | impl ::std::fmt::Display for Combinator { 205 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 206 | write!(f, "{:?}", self) 207 | } 208 | } 209 | 210 | /// 211 | /// Defines group type predicates and parser functions. 212 | /// 213 | impl Combinator { 214 | /// 215 | /// Returns `true` if self command group is of `ProcessExpr` type. 216 | /// 217 | pub fn is_process_expr(self) -> bool { 218 | !self.is_err_expr() && !self.is_initial_expr() 219 | } 220 | 221 | /// 222 | /// Returns `true` if self command group is of `ErrExpr` type. 223 | /// 224 | pub fn is_err_expr(self) -> bool { 225 | matches!(self, Self::Or | Self::OrElse | Self::MapErr) 226 | } 227 | 228 | /// 229 | /// Returns `true` if self command group is of `InitialExpr` type. 230 | /// 231 | pub fn is_initial_expr(self) -> bool { 232 | matches!(self, Self::Initial) 233 | } 234 | 235 | /// 236 | /// Returns `true` if command group can be a wrapper. 237 | /// 238 | pub fn can_be_wrapper(self) -> bool { 239 | matches!( 240 | self, 241 | Self::Map 242 | | Self::AndThen 243 | | Self::Filter 244 | | Self::Inspect 245 | | Self::FilterMap 246 | | Self::Find 247 | | Self::FindMap 248 | | Self::Partition 249 | | Self::OrElse 250 | | Self::MapErr 251 | ) 252 | } 253 | } 254 | 255 | #[cfg(test)] 256 | mod tests { 257 | use super::*; 258 | 259 | #[test] 260 | fn it_tests_can_be_wrapper() { 261 | assert!(Combinator::Map.can_be_wrapper()); 262 | assert!(Combinator::AndThen.can_be_wrapper()); 263 | assert!(Combinator::Filter.can_be_wrapper()); 264 | assert!(Combinator::Inspect.can_be_wrapper()); 265 | assert!(Combinator::FilterMap.can_be_wrapper()); 266 | assert!(Combinator::Find.can_be_wrapper()); 267 | assert!(Combinator::FindMap.can_be_wrapper()); 268 | assert!(Combinator::Partition.can_be_wrapper()); 269 | assert!(Combinator::OrElse.can_be_wrapper()); 270 | assert!(Combinator::MapErr.can_be_wrapper()); 271 | 272 | assert!(!Combinator::Flatten.can_be_wrapper()); 273 | assert!(!Combinator::Dot.can_be_wrapper()); 274 | assert!(!Combinator::Then.can_be_wrapper()); 275 | assert!(!Combinator::Chain.can_be_wrapper()); 276 | assert!(!Combinator::Collect.can_be_wrapper()); 277 | assert!(!Combinator::Enumerate.can_be_wrapper()); 278 | assert!(!Combinator::Fold.can_be_wrapper()); 279 | assert!(!Combinator::TryFold.can_be_wrapper()); 280 | assert!(!Combinator::Unzip.can_be_wrapper()); 281 | assert!(!Combinator::Zip.can_be_wrapper()); 282 | assert!(!Combinator::UNWRAP.can_be_wrapper()); 283 | } 284 | 285 | // TODO: more tests 286 | } 287 | -------------------------------------------------------------------------------- /join_impl/src/action_expr_chain/builder.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Builder of `Chain`<`ActionExpr`>. 3 | //! 4 | 5 | use quote::ToTokens; 6 | use std::fmt::Debug; 7 | use syn::{ 8 | parse::{Parse, ParseStream}, 9 | Expr::Let, 10 | Pat, Token, 11 | }; 12 | 13 | use super::ActionExprChain; 14 | use crate::{ 15 | chain::{ 16 | expr::InnerExpr, 17 | group::{ActionGroup, ApplicationType, Combinator, GroupDeterminer, MoveType}, 18 | Chain, ParseChain, 19 | }, 20 | parse::{ 21 | unit::{ParseUnit, Unit, UnitResult}, 22 | utils::{is_block_expr, parse_until}, 23 | }, 24 | }; 25 | 26 | pub struct ActionExprChainBuilder<'a> { 27 | group_determiners: &'a [GroupDeterminer], 28 | 29 | deferred_determiner: &'a GroupDeterminer, 30 | wrapper_determiner: &'a GroupDeterminer, 31 | } 32 | 33 | impl<'a> ActionExprChainBuilder<'a> { 34 | /// 35 | /// Creates new `ActionExprChainBuilderOutput` with given `GroupDeterminer`'s. 36 | /// 37 | pub fn new( 38 | group_determiners: &'a [GroupDeterminer], 39 | deferred_determiner: &'a GroupDeterminer, 40 | wrapper_determiner: &'a GroupDeterminer, 41 | ) -> Self { 42 | Self { 43 | group_determiners, 44 | deferred_determiner, 45 | wrapper_determiner, 46 | } 47 | } 48 | } 49 | 50 | impl<'a> ParseChain for ActionExprChainBuilder<'a> { 51 | /// 52 | /// Builds new `ActionExprChainBuilder` from input `ParseStream`. 53 | /// 54 | fn build_from_parse_stream(&self, input: ParseStream<'_>) -> syn::Result { 55 | let mut chain = ActionExprChain::new(None, &[]); 56 | let mut action_group = ActionGroup::new( 57 | Combinator::Initial, 58 | ApplicationType::Instant, 59 | MoveType::None, 60 | ); 61 | let mut member_idx = 0; 62 | let mut wrapper_count = 0isize; 63 | 64 | loop { 65 | let Unit { 66 | parsed: mut action_expr, 67 | next, 68 | } = action_group.parse_stream(self, input)?; 69 | 70 | if member_idx == 0 { 71 | // Because first expr is `Initial` 72 | let exprs = action_expr.inner_exprs().expect( 73 | "join: Failed to extract initial expr. This's a bug, please report it.", 74 | ); 75 | 76 | // If we have branch starting with `let` pattern, 77 | // check if it's correct and then, if it's, associate 78 | // it with given branch 79 | if let Let(let_expr) = exprs 80 | .first() 81 | .cloned() 82 | .expect("join: Failed to extract first expr of initial expr. This's a bug, please report it.") 83 | { 84 | if let Pat::Ident(pat) = &let_expr.pat { 85 | chain.set_id(Some(pat.clone())); 86 | } else { 87 | return Err(input.error("Incorrect `let` pattern")); 88 | } 89 | 90 | action_expr = action_expr 91 | .replace_inner_exprs(&[*let_expr.expr.clone()]) 92 | .expect("join: Failed to replace initial expr. This's a bug, please report it."); 93 | } 94 | 95 | if action_expr 96 | .inner_exprs() 97 | .expect("join: Failed to extract initial expr. This's a bug, please report it.") 98 | .first() 99 | .expect("join: Failed to extract first expr of initial expr. This's a bug, please report it.") 100 | .into_token_stream() 101 | .is_empty() 102 | { 103 | return Err(input.error("Chain first expr can't be empty")); 104 | } 105 | } 106 | 107 | chain.append_member(action_expr); 108 | 109 | if let Some(next) = next { 110 | wrapper_count += match next.move_type { 111 | MoveType::Wrap => 1, 112 | MoveType::Unwrap => -1, 113 | _ => 0, 114 | }; 115 | if wrapper_count < 0 { 116 | break Err(input.error("Unexpected `<<<`")); 117 | } 118 | 119 | action_group = next; 120 | member_idx += 1; 121 | } else { 122 | break if chain.is_empty() { 123 | Err(input.error("Chain can't be empty")) 124 | } else { 125 | if chain 126 | .members() 127 | .last() 128 | .expect("join: Failed to extract last `ExprGroup` member. This's a bug, please report it.") 129 | .inner_exprs() 130 | .and_then( 131 | |val| 132 | val 133 | .last() 134 | ) 135 | .map(is_block_expr) 136 | .unwrap_or(false) 137 | { 138 | input.parse::>()?; 139 | } else if !input.is_empty() { 140 | input.parse::()?; 141 | } 142 | Ok(chain) 143 | }; 144 | } 145 | } 146 | } 147 | } 148 | 149 | impl<'a> ParseUnit for ActionExprChainBuilder<'a> { 150 | /// 151 | /// Parses unit using self `GroupDeterminer`'s to determine unit end. 152 | /// 153 | fn parse_unit( 154 | &self, 155 | input: ParseStream, 156 | allow_empty_parsed: bool, 157 | ) -> UnitResult { 158 | parse_until( 159 | input, 160 | self.group_determiners.iter(), 161 | self.deferred_determiner, 162 | self.wrapper_determiner, 163 | allow_empty_parsed, 164 | ) 165 | } 166 | } 167 | 168 | #[cfg(test)] 169 | mod tests { 170 | use super::{ 171 | super::super::join::parse::{ 172 | DEFAULT_GROUP_DETERMINERS, DEFERRED_DETERMINER, WRAPPER_DETERMINER, 173 | }, 174 | *, 175 | }; 176 | use crate::chain::{ 177 | expr::{ActionExpr, ErrExpr, InitialExpr, ProcessExpr}, 178 | group::{ApplicationType, Combinator, ExprGroup, MoveType}, 179 | }; 180 | use ::quote::quote; 181 | use ::syn::{parse::Parse, parse2, parse_quote}; 182 | use syn::parse::ParseStream; 183 | 184 | impl Parse for ActionExprChain { 185 | fn parse(input: ParseStream) -> syn::Result { 186 | let builder = ActionExprChainBuilder::new( 187 | DEFAULT_GROUP_DETERMINERS, 188 | DEFERRED_DETERMINER, 189 | WRAPPER_DETERMINER, 190 | ); 191 | builder.build_from_parse_stream(input) 192 | } 193 | } 194 | 195 | #[test] 196 | fn it_parses_chain_from_3_process_expr() { 197 | let chain: ActionExprChain = parse_quote! { Ok(2) => |v| Ok(v + 1) |> |v| v + 2 }; 198 | assert_eq!(chain.members().len(), 3); 199 | assert!(chain.id().is_none()); 200 | } 201 | 202 | #[test] 203 | fn it_parses_chain_from_3_process_expr_and_1_err_expr() { 204 | let chain: ActionExprChain = parse_quote! { Ok(2) => |v| Ok(v + 1) |> |v| v + 2 <| Ok(2) }; 205 | assert_eq!(chain.members().len(), 4); 206 | assert!(chain.id().is_none()); 207 | } 208 | 209 | #[test] 210 | fn it_parses_chain_from_initial_expr() { 211 | let chain: ActionExprChain = parse_quote! { Ok(2) }; 212 | assert_eq!(chain.members().len(), 1); 213 | assert!(chain.id().is_none()); 214 | } 215 | 216 | #[test] 217 | fn it_attempts_to_parse_empty_chain() { 218 | assert!(parse2::(quote! {}).is_err()); 219 | } 220 | 221 | #[test] 222 | fn it_parses_chain_with_all_possible_command_expressions() { 223 | let chain: ActionExprChain = parse_quote! { 224 | Ok(2) => |_| Ok(3) |> |_| 4 <| Ok(5) => |v| Ok(v) <= |_| Ok(8) ?? |v| println!("{}", v) -> |v| v 225 | }; 226 | 227 | let members = chain.members().to_vec(); 228 | 229 | assert_eq!( 230 | members[0], 231 | ExprGroup::new( 232 | ActionExpr::Initial(InitialExpr::Single([parse_quote! { Ok(2) }])), 233 | ActionGroup::new( 234 | Combinator::Initial, 235 | ApplicationType::Instant, 236 | MoveType::None 237 | ) 238 | ) 239 | ); 240 | 241 | assert_eq!( 242 | members[1], 243 | ExprGroup::new( 244 | ActionExpr::Process(ProcessExpr::AndThen([parse_quote! { |_| Ok(3) }])), 245 | ActionGroup::new( 246 | Combinator::AndThen, 247 | ApplicationType::Instant, 248 | MoveType::None 249 | ) 250 | ) 251 | ); 252 | 253 | assert_eq!( 254 | members[2], 255 | ExprGroup::new( 256 | ActionExpr::Process(ProcessExpr::Map([parse_quote! { |_| 4 }])), 257 | ActionGroup::new(Combinator::Map, ApplicationType::Instant, MoveType::None) 258 | ) 259 | ); 260 | 261 | assert_eq!( 262 | members[3], 263 | ExprGroup::new( 264 | ActionExpr::Err(ErrExpr::Or([parse_quote! { Ok(5) }])), 265 | ActionGroup::new(Combinator::Or, ApplicationType::Instant, MoveType::None) 266 | ) 267 | ); 268 | 269 | assert_eq!( 270 | members[4], 271 | ExprGroup::new( 272 | ActionExpr::Process(ProcessExpr::AndThen([parse_quote! { |v| Ok(v) }])), 273 | ActionGroup::new( 274 | Combinator::AndThen, 275 | ApplicationType::Instant, 276 | MoveType::None 277 | ) 278 | ) 279 | ); 280 | 281 | assert_eq!( 282 | members[5], 283 | ExprGroup::new( 284 | ActionExpr::Err(ErrExpr::OrElse([parse_quote! { |_| Ok(8) }])), 285 | ActionGroup::new(Combinator::OrElse, ApplicationType::Instant, MoveType::None) 286 | ) 287 | ); 288 | 289 | assert_eq!( 290 | members[6], 291 | ExprGroup::new( 292 | ActionExpr::Process(ProcessExpr::Inspect([ 293 | parse_quote! { |v| println!("{}", v) } 294 | ])), 295 | ActionGroup::new( 296 | Combinator::Inspect, 297 | ApplicationType::Instant, 298 | MoveType::None 299 | ) 300 | ) 301 | ); 302 | 303 | assert_eq!( 304 | members[7], 305 | ExprGroup::new( 306 | ActionExpr::Process(ProcessExpr::Then([parse_quote! { |v| v }])), 307 | ActionGroup::new(Combinator::Then, ApplicationType::Instant, MoveType::None) 308 | ) 309 | ); 310 | } 311 | 312 | #[test] 313 | fn it_parses_chain_with_all_possible_deferred_command_expressions() { 314 | let chain: ActionExprChain = parse_quote! { 315 | Ok(2) ~=> |_| Ok(3) ~|> |_| 4 ~<| Ok(5) ~=> |v| Ok(v) ~<= |_| Ok(8) ~?? |v| println!("{}", v) ~-> |v| v 316 | }; 317 | 318 | let members = chain.members().to_vec(); 319 | 320 | assert_eq!( 321 | members[0], 322 | ExprGroup::new( 323 | ActionExpr::Initial(InitialExpr::Single([parse_quote! { Ok(2) }])), 324 | ActionGroup::new( 325 | Combinator::Initial, 326 | ApplicationType::Instant, 327 | MoveType::None 328 | ) 329 | ) 330 | ); 331 | 332 | assert_eq!( 333 | members[1], 334 | ExprGroup::new( 335 | ActionExpr::Process(ProcessExpr::AndThen([parse_quote! { |_| Ok(3) }])), 336 | ActionGroup::new( 337 | Combinator::AndThen, 338 | ApplicationType::Deferred, 339 | MoveType::None 340 | ) 341 | ) 342 | ); 343 | 344 | assert_eq!( 345 | members[2], 346 | ExprGroup::new( 347 | ActionExpr::Process(ProcessExpr::Map([parse_quote! { |_| 4 }])), 348 | ActionGroup::new(Combinator::Map, ApplicationType::Deferred, MoveType::None) 349 | ) 350 | ); 351 | 352 | assert_eq!( 353 | members[3], 354 | ExprGroup::new( 355 | ActionExpr::Err(ErrExpr::Or([parse_quote! { Ok(5) }])), 356 | ActionGroup::new(Combinator::Or, ApplicationType::Deferred, MoveType::None) 357 | ) 358 | ); 359 | 360 | assert_eq!( 361 | members[4], 362 | ExprGroup::new( 363 | ActionExpr::Process(ProcessExpr::AndThen([parse_quote! { |v| Ok(v) }])), 364 | ActionGroup::new( 365 | Combinator::AndThen, 366 | ApplicationType::Deferred, 367 | MoveType::None 368 | ) 369 | ) 370 | ); 371 | 372 | assert_eq!( 373 | members[5], 374 | ExprGroup::new( 375 | ActionExpr::Err(ErrExpr::OrElse([parse_quote! { |_| Ok(8) }])), 376 | ActionGroup::new( 377 | Combinator::OrElse, 378 | ApplicationType::Deferred, 379 | MoveType::None 380 | ) 381 | ) 382 | ); 383 | 384 | assert_eq!( 385 | members[6], 386 | ExprGroup::new( 387 | ActionExpr::Process(ProcessExpr::Inspect([ 388 | parse_quote! { |v| println!("{}", v) } 389 | ])), 390 | ActionGroup::new( 391 | Combinator::Inspect, 392 | ApplicationType::Deferred, 393 | MoveType::None 394 | ) 395 | ) 396 | ); 397 | 398 | assert_eq!( 399 | members[7], 400 | ExprGroup::new( 401 | ActionExpr::Process(ProcessExpr::Then([parse_quote! { |v| v }])), 402 | ActionGroup::new(Combinator::Then, ApplicationType::Deferred, MoveType::None) 403 | ) 404 | ); 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /join_impl/src/chain/group/action_group.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Definition of `ActionGroup` with parser's mappings. 3 | //! 4 | 5 | use super::{ApplicationType, Combinator, ExprGroup, MoveType}; 6 | use crate::{ 7 | chain::expr::{ActionExpr, ErrExpr, InitialExpr, ProcessExpr}, 8 | parse::{ 9 | empty::Empty, 10 | unit::{ParseUnit, Unit, UnitResult}, 11 | }, 12 | }; 13 | use syn::{parse::ParseStream, parse_quote}; 14 | 15 | /// 16 | /// `Combinator` with configuration. 17 | /// 18 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 19 | pub struct ActionGroup { 20 | pub combinator: Combinator, 21 | pub application_type: ApplicationType, 22 | pub move_type: MoveType, 23 | } 24 | 25 | impl ActionGroup { 26 | /// 27 | /// Creates new `ActionGroup` using provided configuration. 28 | /// 29 | pub fn new( 30 | combinator: Combinator, 31 | application_type: ApplicationType, 32 | move_type: MoveType, 33 | ) -> Self { 34 | Self { 35 | combinator, 36 | application_type, 37 | move_type, 38 | } 39 | } 40 | 41 | /// 42 | /// Parses `ParseStream` as `ExprGroup`<`ActionExpr`> using given `ParseUnit`. 43 | /// 44 | pub fn parse_stream( 45 | &self, 46 | unit_parser: &impl ParseUnit, 47 | input: ParseStream<'_>, 48 | ) -> UnitResult, ActionGroup> { 49 | let &Self { 50 | combinator, 51 | move_type, 52 | .. 53 | } = self; 54 | 55 | if move_type == MoveType::Wrap { 56 | self.to_wrapper_action_expr() 57 | .ok_or_else(|| { 58 | input.error(format!("combinator {:?} can't be a wrapper", combinator)) 59 | }) 60 | .and_then(|val| { 61 | Ok(Unit { 62 | parsed: val, 63 | next: unit_parser.parse_unit::(input, true)?.next, 64 | }) 65 | }) 66 | } else { 67 | self.parse_action_expr(unit_parser, input) 68 | } 69 | } 70 | 71 | /// 72 | /// Attempts to build a wrapper expression from `Self` using provided `ActionGroup`. 73 | /// 74 | fn to_wrapper_action_expr(self) -> Option> { 75 | let return_val = parse_quote! { |__v| __v }; 76 | 77 | Some(ExprGroup::new( 78 | match self.combinator { 79 | Combinator::Map => ActionExpr::Process(ProcessExpr::Map([return_val])), 80 | Combinator::AndThen => ActionExpr::Process(ProcessExpr::AndThen([return_val])), 81 | Combinator::Filter => ActionExpr::Process(ProcessExpr::Filter([return_val])), 82 | Combinator::Inspect => ActionExpr::Process(ProcessExpr::Inspect([return_val])), 83 | Combinator::FilterMap => ActionExpr::Process(ProcessExpr::FilterMap([return_val])), 84 | Combinator::Find => ActionExpr::Process(ProcessExpr::Find([return_val])), 85 | Combinator::FindMap => ActionExpr::Process(ProcessExpr::FindMap([return_val])), 86 | Combinator::Partition => ActionExpr::Process(ProcessExpr::Partition([return_val])), 87 | Combinator::OrElse => ActionExpr::Err(ErrExpr::OrElse([return_val])), 88 | Combinator::MapErr => ActionExpr::Err(ErrExpr::MapErr([return_val])), 89 | _ => return None, 90 | }, 91 | self, 92 | )) 93 | } 94 | 95 | /// 96 | /// Attempts to parse given `ParseStream` as `ExprGroup`<`ActionExpr`>. 97 | /// 98 | #[cfg(not(feature = "full"))] 99 | fn parse_action_expr( 100 | &self, 101 | unit_parser: &impl ParseUnit, 102 | input: ParseStream, 103 | ) -> UnitResult, ActionGroup> { 104 | match self.combinator { 105 | Combinator::Map => { 106 | ExprGroup::parse_single_unit(ProcessExpr::Map, unit_parser, self, input) 107 | } 108 | Combinator::AndThen => { 109 | ExprGroup::parse_single_unit(ProcessExpr::AndThen, unit_parser, self, input) 110 | } 111 | Combinator::Filter => { 112 | ExprGroup::parse_single_unit(ProcessExpr::Filter, unit_parser, self, input) 113 | } 114 | Combinator::Flatten => { 115 | ExprGroup::parse_empty_unit(ProcessExpr::Flatten, unit_parser, self, input) 116 | } 117 | Combinator::Dot => { 118 | ExprGroup::parse_single_unit(ProcessExpr::Dot, unit_parser, self, input) 119 | } 120 | Combinator::Then => { 121 | ExprGroup::parse_single_unit(ProcessExpr::Then, unit_parser, self, input) 122 | } 123 | Combinator::Inspect => { 124 | ExprGroup::parse_single_unit(ProcessExpr::Inspect, unit_parser, self, input) 125 | } 126 | Combinator::Chain => { 127 | ExprGroup::parse_single_unit(ProcessExpr::Chain, unit_parser, self, input) 128 | } 129 | Combinator::Collect => ExprGroup::parse_single_or_empty_unit( 130 | ProcessExpr::Collect, 131 | unit_parser, 132 | self, 133 | input, 134 | ), 135 | Combinator::Enumerate => { 136 | ExprGroup::parse_empty_unit(ProcessExpr::Enumerate, unit_parser, self, input) 137 | } 138 | Combinator::FilterMap => { 139 | ExprGroup::parse_single_unit(ProcessExpr::FilterMap, unit_parser, self, input) 140 | } 141 | Combinator::Find => { 142 | ExprGroup::parse_single_unit(ProcessExpr::Find, unit_parser, self, input) 143 | } 144 | Combinator::Fold => { 145 | ExprGroup::parse_double_unit(ProcessExpr::Fold, unit_parser, self, input) 146 | } 147 | Combinator::FindMap => { 148 | ExprGroup::parse_single_unit(ProcessExpr::FindMap, unit_parser, self, input) 149 | } 150 | Combinator::Partition => { 151 | ExprGroup::parse_single_unit(ProcessExpr::Partition, unit_parser, self, input) 152 | } 153 | Combinator::TryFold => { 154 | ExprGroup::parse_double_unit(ProcessExpr::TryFold, unit_parser, self, input) 155 | } 156 | Combinator::Unzip => { 157 | ExprGroup::parse_quatro_or_empty_unit(ProcessExpr::Unzip, unit_parser, self, input) 158 | } 159 | Combinator::Zip => { 160 | ExprGroup::parse_single_unit(ProcessExpr::Zip, unit_parser, self, input) 161 | } 162 | Combinator::UNWRAP => { 163 | ExprGroup::parse_empty_unit(ProcessExpr::UNWRAP, unit_parser, self, input) 164 | } 165 | Combinator::Or => ExprGroup::parse_single_unit(ErrExpr::Or, unit_parser, self, input), 166 | Combinator::OrElse => { 167 | ExprGroup::parse_single_unit(ErrExpr::OrElse, unit_parser, self, input) 168 | } 169 | Combinator::MapErr => { 170 | ExprGroup::parse_single_unit(ErrExpr::MapErr, unit_parser, self, input) 171 | } 172 | Combinator::Initial => { 173 | ExprGroup::parse_single_unit(InitialExpr::Single, unit_parser, self, input) 174 | } 175 | } 176 | } 177 | 178 | /// 179 | /// Attempts to parse given `ParseStream` as `ExprGroup`<`ActionExpr`>. 180 | /// 181 | #[cfg(feature = "full")] 182 | fn parse_action_expr( 183 | &self, 184 | unit_parser: &impl ParseUnit, 185 | input: ParseStream, 186 | ) -> UnitResult, ActionGroup> { 187 | match self.combinator { 188 | Combinator::Map => { 189 | ExprGroup::parse_single_unit(ProcessExpr::Map, unit_parser, self, input) 190 | } 191 | Combinator::AndThen => { 192 | ExprGroup::parse_single_unit(ProcessExpr::AndThen, unit_parser, self, input) 193 | } 194 | Combinator::Filter => { 195 | ExprGroup::parse_single_unit(ProcessExpr::Filter, unit_parser, self, input) 196 | } 197 | Combinator::Dot => { 198 | ExprGroup::parse_single_unit(ProcessExpr::Dot, unit_parser, self, input) 199 | } 200 | Combinator::Then => { 201 | ExprGroup::parse_single_unit(ProcessExpr::Then, unit_parser, self, input) 202 | } 203 | Combinator::Inspect => { 204 | ExprGroup::parse_single_unit(ProcessExpr::Inspect, unit_parser, self, input) 205 | } 206 | Combinator::All => { 207 | ExprGroup::parse_single_unit(ProcessExpr::All, unit_parser, self, input) 208 | } 209 | Combinator::Any => { 210 | ExprGroup::parse_single_unit(ProcessExpr::Any, unit_parser, self, input) 211 | } 212 | Combinator::ByRef => { 213 | ExprGroup::parse_empty_unit(ProcessExpr::ByRef, unit_parser, self, input) 214 | } 215 | Combinator::Chain => { 216 | ExprGroup::parse_single_unit(ProcessExpr::Chain, unit_parser, self, input) 217 | } 218 | Combinator::Cloned => { 219 | ExprGroup::parse_empty_unit(ProcessExpr::Cloned, unit_parser, self, input) 220 | } 221 | Combinator::Cmp => { 222 | ExprGroup::parse_single_unit(ProcessExpr::Cmp, unit_parser, self, input) 223 | } 224 | Combinator::Collect => ExprGroup::parse_single_or_empty_unit( 225 | ProcessExpr::Collect, 226 | unit_parser, 227 | self, 228 | input, 229 | ), 230 | Combinator::Copied => { 231 | ExprGroup::parse_empty_unit(ProcessExpr::Copied, unit_parser, self, input) 232 | } 233 | Combinator::Count => { 234 | ExprGroup::parse_empty_unit(ProcessExpr::Count, unit_parser, self, input) 235 | } 236 | Combinator::Cycle => { 237 | ExprGroup::parse_empty_unit(ProcessExpr::Cycle, unit_parser, self, input) 238 | } 239 | Combinator::Enumerate => { 240 | ExprGroup::parse_empty_unit(ProcessExpr::Enumerate, unit_parser, self, input) 241 | } 242 | Combinator::Eq => { 243 | ExprGroup::parse_single_unit(ProcessExpr::Eq, unit_parser, self, input) 244 | } 245 | Combinator::FilterMap => { 246 | ExprGroup::parse_single_unit(ProcessExpr::FilterMap, unit_parser, self, input) 247 | } 248 | Combinator::Find => { 249 | ExprGroup::parse_single_unit(ProcessExpr::Find, unit_parser, self, input) 250 | } 251 | Combinator::FindMap => { 252 | ExprGroup::parse_single_unit(ProcessExpr::FindMap, unit_parser, self, input) 253 | } 254 | Combinator::FlatMap => { 255 | ExprGroup::parse_single_unit(ProcessExpr::FlatMap, unit_parser, self, input) 256 | } 257 | Combinator::Flatten => { 258 | ExprGroup::parse_empty_unit(ProcessExpr::Flatten, unit_parser, self, input) 259 | } 260 | Combinator::Fold => { 261 | ExprGroup::parse_double_unit(ProcessExpr::Fold, unit_parser, self, input) 262 | } 263 | Combinator::ForEach => { 264 | ExprGroup::parse_single_unit(ProcessExpr::ForEach, unit_parser, self, input) 265 | } 266 | Combinator::Fuse => { 267 | ExprGroup::parse_empty_unit(ProcessExpr::Fuse, unit_parser, self, input) 268 | } 269 | Combinator::Ge => { 270 | ExprGroup::parse_single_unit(ProcessExpr::Ge, unit_parser, self, input) 271 | } 272 | Combinator::Gt => { 273 | ExprGroup::parse_single_unit(ProcessExpr::Gt, unit_parser, self, input) 274 | } 275 | Combinator::IsSorted => { 276 | ExprGroup::parse_empty_unit(ProcessExpr::IsSorted, unit_parser, self, input) 277 | } 278 | Combinator::IsSortedBy => { 279 | ExprGroup::parse_single_unit(ProcessExpr::IsSortedBy, unit_parser, self, input) 280 | } 281 | Combinator::IsSortedByKey => { 282 | ExprGroup::parse_single_unit(ProcessExpr::IsSortedByKey, unit_parser, self, input) 283 | } 284 | Combinator::IsPartitioned => { 285 | ExprGroup::parse_empty_unit(ProcessExpr::IsPartitioned, unit_parser, self, input) 286 | } 287 | Combinator::Last => { 288 | ExprGroup::parse_empty_unit(ProcessExpr::Last, unit_parser, self, input) 289 | } 290 | Combinator::Le => { 291 | ExprGroup::parse_single_unit(ProcessExpr::Le, unit_parser, self, input) 292 | } 293 | Combinator::Lt => { 294 | ExprGroup::parse_single_unit(ProcessExpr::Lt, unit_parser, self, input) 295 | } 296 | Combinator::Max => { 297 | ExprGroup::parse_empty_unit(ProcessExpr::Max, unit_parser, self, input) 298 | } 299 | Combinator::MaxBy => { 300 | ExprGroup::parse_single_unit(ProcessExpr::MaxBy, unit_parser, self, input) 301 | } 302 | Combinator::MaxByKey => { 303 | ExprGroup::parse_single_unit(ProcessExpr::MaxByKey, unit_parser, self, input) 304 | } 305 | Combinator::Min => { 306 | ExprGroup::parse_empty_unit(ProcessExpr::Min, unit_parser, self, input) 307 | } 308 | Combinator::MinBy => { 309 | ExprGroup::parse_single_unit(ProcessExpr::MinBy, unit_parser, self, input) 310 | } 311 | Combinator::MinByKey => { 312 | ExprGroup::parse_single_unit(ProcessExpr::MinByKey, unit_parser, self, input) 313 | } 314 | Combinator::Ne => { 315 | ExprGroup::parse_single_unit(ProcessExpr::Ne, unit_parser, self, input) 316 | } 317 | Combinator::Nth => { 318 | ExprGroup::parse_single_unit(ProcessExpr::Nth, unit_parser, self, input) 319 | } 320 | Combinator::PartialCmp => { 321 | ExprGroup::parse_single_unit(ProcessExpr::PartialCmp, unit_parser, self, input) 322 | } 323 | Combinator::Partition => { 324 | ExprGroup::parse_single_unit(ProcessExpr::Partition, unit_parser, self, input) 325 | } 326 | Combinator::PartitionInPlace => ExprGroup::parse_single_unit( 327 | ProcessExpr::PartitionInPlace, 328 | unit_parser, 329 | self, 330 | input, 331 | ), 332 | Combinator::Peekable => { 333 | ExprGroup::parse_empty_unit(ProcessExpr::Peekable, unit_parser, self, input) 334 | } 335 | Combinator::Position => { 336 | ExprGroup::parse_single_unit(ProcessExpr::Position, unit_parser, self, input) 337 | } 338 | Combinator::Product => { 339 | ExprGroup::parse_empty_unit(ProcessExpr::Product, unit_parser, self, input) 340 | } 341 | Combinator::Rev => { 342 | ExprGroup::parse_empty_unit(ProcessExpr::Rev, unit_parser, self, input) 343 | } 344 | Combinator::Rposition => { 345 | ExprGroup::parse_single_unit(ProcessExpr::Rposition, unit_parser, self, input) 346 | } 347 | Combinator::Scan => { 348 | ExprGroup::parse_single_unit(ProcessExpr::Scan, unit_parser, self, input) 349 | } 350 | Combinator::SizeHint => { 351 | ExprGroup::parse_empty_unit(ProcessExpr::SizeHint, unit_parser, self, input) 352 | } 353 | Combinator::Skip => { 354 | ExprGroup::parse_single_unit(ProcessExpr::Skip, unit_parser, self, input) 355 | } 356 | Combinator::SkipWhile => { 357 | ExprGroup::parse_single_unit(ProcessExpr::SkipWhile, unit_parser, self, input) 358 | } 359 | Combinator::StepBy => { 360 | ExprGroup::parse_single_unit(ProcessExpr::StepBy, unit_parser, self, input) 361 | } 362 | Combinator::Sum => { 363 | ExprGroup::parse_empty_unit(ProcessExpr::Sum, unit_parser, self, input) 364 | } 365 | Combinator::Take => { 366 | ExprGroup::parse_single_unit(ProcessExpr::Take, unit_parser, self, input) 367 | } 368 | Combinator::TakeWhile => { 369 | ExprGroup::parse_single_unit(ProcessExpr::TakeWhile, unit_parser, self, input) 370 | } 371 | Combinator::TryFold => { 372 | ExprGroup::parse_double_unit(ProcessExpr::TryFold, unit_parser, self, input) 373 | } 374 | Combinator::TryForEach => { 375 | ExprGroup::parse_single_unit(ProcessExpr::TryForEach, unit_parser, self, input) 376 | } 377 | Combinator::Unzip => { 378 | ExprGroup::parse_quatro_or_empty_unit(ProcessExpr::Unzip, unit_parser, self, input) 379 | } 380 | Combinator::Zip => { 381 | ExprGroup::parse_single_unit(ProcessExpr::Zip, unit_parser, self, input) 382 | } 383 | Combinator::UNWRAP => { 384 | ExprGroup::parse_empty_unit(ProcessExpr::UNWRAP, unit_parser, self, input) 385 | } 386 | Combinator::Or => ExprGroup::parse_single_unit(ErrExpr::Or, unit_parser, self, input), 387 | Combinator::OrElse => { 388 | ExprGroup::parse_single_unit(ErrExpr::OrElse, unit_parser, self, input) 389 | } 390 | Combinator::MapErr => { 391 | ExprGroup::parse_single_unit(ErrExpr::MapErr, unit_parser, self, input) 392 | } 393 | Combinator::Initial => { 394 | ExprGroup::parse_single_unit(InitialExpr::Single, unit_parser, self, input) 395 | } 396 | } 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /join/tests/join.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[allow(clippy::unused_unit)] 3 | mod join_tests { 4 | use join::{join, try_join}; 5 | use std::{convert::identity, error::Error}; 6 | 7 | type BoxedError = Box; 8 | 9 | type Result = std::result::Result; 10 | 11 | type _Result = std::result::Result; 12 | 13 | fn three() -> u16 { 14 | 3 15 | } 16 | 17 | #[allow(clippy::unnecessary_wraps)] 18 | fn ok_four() -> Result { 19 | Ok(4) 20 | } 21 | 22 | #[allow(clippy::unnecessary_wraps)] 23 | fn some_five() -> Option { 24 | Some(5) 25 | } 26 | 27 | fn make_err() -> Result { 28 | Err("error".to_string().into()) 29 | } 30 | 31 | fn none() -> Option { 32 | None 33 | } 34 | 35 | fn add_one(v: u16) -> u16 { 36 | v + 1 37 | } 38 | 39 | #[allow(clippy::unnecessary_wraps)] 40 | fn add_one_ok(v: u16) -> Result { 41 | Ok(add_one(v)) 42 | } 43 | 44 | fn to_err(v: u16) -> Result { 45 | Err(v.to_string().into()) 46 | } 47 | 48 | fn to_none(_: u16) -> Option { 49 | None 50 | } 51 | 52 | #[test] 53 | fn it_creates_nested_chains() { 54 | let value = try_join! { 55 | Ok::<_,u8>(Ok::<_,u8>(Some(2u16))) 56 | => >>> 57 | => >>> 58 | |> |v| v + 2 59 | ..ok_or(4) 60 | <<< 61 | |> |v| Ok::<_,u8>(v + 5) 62 | <<< 63 | }; 64 | 65 | assert_eq!(value.unwrap(), Ok(9)); 66 | 67 | let value = join! { 68 | Some(Some(Some(2u16))) 69 | |> >>> 70 | |> >>> 71 | |> |v| v + 2 72 | ..ok_or(4) 73 | .unwrap() 74 | <<< 75 | |> |v| Some(v + 5) 76 | <<< 77 | }; 78 | 79 | assert_eq!(value, Some(Some(Some(9)))); 80 | 81 | let value = try_join! { 82 | Some(Some(Some(2u16))) 83 | => >>> 84 | => >>> 85 | |> >>> 86 | }; 87 | 88 | assert_eq!(value, Some(2)); 89 | 90 | let value = try_join! { 91 | Some(Some(Some(2u16))) 92 | => >>> 93 | => >>> 94 | |> >>> 95 | <<< 96 | <<< 97 | <<< 98 | }; 99 | 100 | assert_eq!(value, Some(2)); 101 | 102 | let value = try_join! { 103 | Some(Some(Some(2u16))) 104 | => >>> 105 | => >>> 106 | |> >>> 107 | ~=> |v| Some(v * 2) 108 | }; 109 | 110 | assert_eq!(value, Some(4)); 111 | 112 | let mut captured = Some(2); 113 | 114 | let value = try_join! { 115 | Ok::<_,u8>(Ok::<_,u8>(Some(2u16))) 116 | => >>> 117 | |> |v| { captured = None; v } 118 | => >>> 119 | |> { let captured = *captured.as_ref().unwrap(); move |v| v + captured } 120 | ..ok_or(4) 121 | <<< 122 | |> |v| Ok::<_,u8>(v + 5) 123 | <<< 124 | }; 125 | 126 | assert_eq!(value.unwrap(), Ok(9)); 127 | } 128 | 129 | #[test] 130 | fn it_produces_n_branches_with_length_1() { 131 | let product = try_join! { 132 | Ok(2u16), 133 | Ok(three()), 134 | ok_four(), 135 | some_five().ok_or_else(|| "error".into()), 136 | map => |a, b, c, d| a * b * c * d 137 | }; 138 | 139 | assert_eq!(product.unwrap(), 120); 140 | 141 | let err = try_join! { 142 | Ok(2), 143 | Ok(three()), 144 | ok_four(), 145 | make_err(), 146 | map => |a, b, c, d| a * b * c * d 147 | }; 148 | 149 | assert_eq!( 150 | err.unwrap_err().to_string(), 151 | make_err().unwrap_err().to_string() 152 | ); 153 | 154 | let product = try_join! { 155 | Some(2), 156 | Some(three()), 157 | some_five(), 158 | ok_four().ok(), 159 | map => |a, b, c, d| a * b * c * d 160 | }; 161 | 162 | assert_eq!(product, Some(120)); 163 | 164 | let none = try_join! { 165 | Some(2), 166 | Some(three()), 167 | some_five(), 168 | none(), 169 | ok_four().ok(), 170 | map => |a, b, c, d, e| a * b * c * d * e 171 | }; 172 | 173 | assert_eq!(none, None); 174 | } 175 | 176 | #[test] 177 | fn it_produces_n_branches_with_any_length() { 178 | let product = try_join! { 179 | Ok(2u16).map(add_one).and_then(add_one_ok), //4 180 | Ok(three()).and_then(add_one_ok).map(add_one).map(add_one).map(add_one), //7 181 | ok_four().map(add_one), //5 182 | some_five().map(add_one).ok_or_else(|| "error".into()).and_then(to_err).and_then(add_one_ok).or(Ok(5)), // 5 183 | map => |a, b, c, d| a * b * c * d 184 | }; 185 | 186 | assert_eq!(product.unwrap(), 700); 187 | 188 | let err = try_join! { 189 | Ok(2).map(add_one), 190 | Ok(three()).and_then(to_err), 191 | ok_four().and_then(|_| -> Result { Err("ERROR".into()) }), 192 | make_err(), 193 | map => |a, b, c, d| a * b * c * d 194 | }; 195 | 196 | assert_eq!( 197 | err.unwrap_err().to_string(), 198 | to_err(three()).unwrap_err().to_string() 199 | ); 200 | } 201 | 202 | #[test] 203 | fn it_produces_n_branches_with_any_length_using_combinators() { 204 | let product = try_join! { 205 | Ok(2u16) |> add_one => add_one_ok, //4 206 | Ok(three()) => add_one_ok |> add_one |> add_one |> add_one, //7 207 | ok_four() |> add_one, //5 208 | some_five() |> add_one ..ok_or_else(|| "error".into()) => to_err => add_one_ok <| Ok(5), // 5 209 | map => |a, b, c, d| a * b * c * d 210 | }; 211 | 212 | assert_eq!(product.unwrap(), 700); 213 | 214 | let sum = try_join! { 215 | 2u16 -> Ok |> add_one => add_one_ok, //4 216 | three() -> Ok => add_one_ok |> add_one |> add_one |> add_one, //7 217 | ok_four() |> add_one, //5 218 | some_five() |> add_one ..ok_or_else(|| "error".into()) => to_err => add_one_ok <| Ok(5), // 5 219 | and_then => |a, b, c, d| Ok(a + b + c + d) 220 | }; 221 | 222 | assert_eq!(sum.unwrap(), 21); 223 | 224 | let err = try_join! { 225 | Ok(2) |> add_one, 226 | Ok(three()) => to_err, 227 | ok_four() => |_| -> Result { Err("ERROR!".into()) }, 228 | make_err(), 229 | map => |a, b, c, d| a * b * c * d 230 | }; 231 | 232 | assert_eq!( 233 | err.unwrap_err().to_string(), 234 | to_err(three()).unwrap_err().to_string() 235 | ); 236 | 237 | let none = try_join! { 238 | 2 -> Some |> add_one, 239 | Some(three()) => to_none, 240 | ok_four() => |_| -> Result { Ok(10) } ..ok(), 241 | none(), 242 | map => |a, b, c, d| a * b * c * d 243 | }; 244 | 245 | assert_eq!(none, None); 246 | } 247 | 248 | #[test] 249 | fn it_tests_handler_behaviour() { 250 | let ok = try_join! { 251 | Ok(2), 252 | Ok(3), 253 | ok_four(), 254 | and_then => |_a, _b, _c| Ok::, _>(None) 255 | }; 256 | 257 | assert_eq!(ok.unwrap(), None); 258 | 259 | let err = try_join! { 260 | Ok(2), 261 | Ok(3), 262 | ok_four(), 263 | and_then => |a: u8, _b, _c| Err::, _>(a.to_string().into()) 264 | }; 265 | 266 | assert_eq!( 267 | err.unwrap_err().to_string(), 268 | Err::("2".into()).unwrap_err().to_string() 269 | ); 270 | 271 | let some = try_join! { 272 | Some(2), 273 | Some(3), 274 | some_five(), 275 | and_then => |a, _b, _c| Some(a) 276 | }; 277 | 278 | assert_eq!(some, Some(2)); 279 | 280 | let none = try_join! { 281 | Some(2), 282 | Some(3), 283 | some_five(), 284 | and_then => |_a, _b, _c| None:: 285 | }; 286 | 287 | assert_eq!(none, None); 288 | 289 | let some = try_join! { 290 | Some(2u16), 291 | Some(3u16), 292 | some_five(), 293 | map => |a, b, c| a + b + c 294 | }; 295 | 296 | assert_eq!(some, Some(10)); 297 | 298 | let none: Option = try_join! { 299 | Some(2u16), 300 | None, 301 | some_five(), 302 | and_then => |a: u16, b: u16, c: u16| -> Option { Some(a + b + c) } 303 | }; 304 | 305 | assert_eq!(none, None); 306 | 307 | let then: Result = Ok(2); 308 | let map: Result = Ok(3); 309 | let and_then = ok_four; 310 | 311 | let ok = join! { 312 | then, 313 | map, 314 | and_then(), 315 | then => |a: Result, b: Result, c: Result| Ok::<_, u8>(a.unwrap() + b.unwrap() + c.unwrap()) 316 | }; 317 | 318 | assert_eq!(ok.unwrap(), 9); 319 | 320 | let err = join! { 321 | Ok(2), 322 | Ok(3), 323 | ok_four(), 324 | then => |a: Result, b: Result, c: Result| Err::(a.unwrap() + b.unwrap() + c.unwrap()) 325 | }; 326 | 327 | assert_eq!(err, Err(9)); 328 | } 329 | 330 | #[test] 331 | fn it_tests_steps() { 332 | let product = try_join! { 333 | let branch_0 = Ok(2u16) ~|> { 334 | let branch_0 = branch_0.as_ref().ok().cloned(); 335 | let branch_1 = branch_1.as_ref().ok().cloned(); 336 | let branch_2 = branch_2.as_ref().ok().cloned(); 337 | let branch_3 = branch_3.as_ref().ok().cloned(); 338 | 339 | move |value| { 340 | assert_eq!(branch_0.unwrap(), value); 341 | assert_eq!(branch_1.unwrap(), 3); 342 | assert_eq!(branch_2.unwrap(), 4); 343 | assert_eq!(branch_3.unwrap(), 6); 344 | add_one(value) 345 | } 346 | } ~=> add_one_ok, //4 347 | let branch_1 = Ok(three()) ~=> add_one_ok ~|> |value| value |> { 348 | let branch_0 = branch_0.as_ref().ok().cloned(); 349 | let branch_1 = branch_1.as_ref().ok().cloned(); 350 | let branch_2 = branch_2.as_ref().ok().cloned(); 351 | let branch_3 = branch_3.as_ref().ok().cloned(); 352 | 353 | move |value| { 354 | assert_eq!(branch_0.unwrap(), 3); 355 | assert_eq!(branch_1.unwrap(), value); 356 | assert_eq!(branch_2.unwrap(), 5); 357 | assert_eq!(branch_3.unwrap(), 5); 358 | add_one(value) 359 | } 360 | } ~|> add_one ~|> add_one, //7 361 | let branch_2 = ok_four() ~|> add_one, //5 362 | let branch_3 = some_five() |> add_one ..ok_or_else(|| "error".into()) ~=> to_err <| Ok(5) ~=> add_one_ok, // 6 363 | map => |a, b, c, d| a * b * c * d 364 | }; 365 | 366 | assert_eq!(product.unwrap(), 840); 367 | } 368 | 369 | #[test] 370 | fn it_produces_tuple() { 371 | let values = try_join! { Ok::<_,u8>(2), Ok::<_,u8>(3) }; 372 | assert_eq!(values.unwrap(), (2, 3)); 373 | } 374 | 375 | #[test] 376 | fn it_tests_multi_step_single_branch() { 377 | let values = 378 | try_join! { vec![1,2,3,4,5,6,7,8,9].into_iter() -> Some ~|> >>> ?> |v| v % 3 != 0 =>[] Vec<_> ~|> |v| v } 379 | .unwrap(); 380 | assert_eq!(values, vec![1, 2, 4, 5, 7, 8]); 381 | } 382 | 383 | #[test] 384 | fn it_checks_evalutation_in_case_of_error() { 385 | let error = try_join! { Err::(2) ~|> |_| unreachable!(), Ok::<_,u8>(3) }; 386 | assert_eq!(error, Err(2)); 387 | } 388 | 389 | #[test] 390 | fn it_tests_iter_combinators() { 391 | let mut some_vec = Some(vec![0u8]); 392 | 393 | let values: (Vec<_>, Vec<_>) = join! { 394 | vec![2u8, 3, 4, 5, 6, 7, 8, 9, 10, 11].into_iter() |> |v| { some_vec = None; v + 1 } ?|> |v| if v % 2 == 0 { Some(v) } else { None } |n> ^@ { some_vec.clone() }, |mut acc, (index, v)| { acc.as_mut().unwrap().push(v + (index as u8)); acc } ..unwrap().into_iter() =>[] Vec<_> ~..into_iter() ?&!> |&n| (n as f64).cos().abs() > ::std::f64::consts::PI / 3f64 395 | }; 396 | 397 | assert_eq!(values, (vec![], vec![0, 4, 7, 10, 13, 16])); 398 | 399 | let values = vec![0, 1u8, 2, 3, 4, 5, 6]; 400 | let other_values = vec![4u8, 5, 6, 7, 8, 9, 10]; 401 | 402 | assert_eq!( 403 | join! { 404 | values.iter() >^> other_values.iter() ?&!> |&(&v, &v1)| v % 2 == 0 && v1 % 2 == 0 405 | }, 406 | ( 407 | vec![(&0u8, &4u8), (&2, &6), (&4, &8), (&6, &10)], 408 | vec![(&1u8, &5u8), (&3, &7), (&5, &9)] 409 | ) 410 | ); 411 | 412 | assert_eq!( 413 | join! { 414 | values.iter() >^> other_values.iter() <-> &u8, &u8, Vec<_>, Vec<_> 415 | }, 416 | (values.iter().collect(), other_values.iter().collect()) 417 | ); 418 | 419 | assert_eq!( 420 | join! { vec![1u8, 2, 3, 4, 5].into_iter() ?> |v| v % 2 != 0 =>[] Vec<_> }, 421 | vec![1u8, 3, 5] 422 | ); 423 | 424 | assert_eq!( 425 | try_join! { let v = vec![1, 2, 3, 4, 5] -> Some ~=> >>> ..into_iter() ?|>@ |v| if v % 2 == 0 { Some(v) } else { None } }, 426 | Some(2) 427 | ); 428 | 429 | assert_eq!( 430 | join! { vec![vec![1, 2, 3], vec![2]].into_iter() ^^> =>[] Vec<_> }, 431 | vec![1, 2, 3, 2] 432 | ); 433 | 434 | assert!( 435 | try_join! { vec![Ok(5), Err(4)].into_iter() ?^@ 0, |acc, v| v.map(|v| acc + v) } 436 | .is_err() 437 | ); 438 | } 439 | 440 | #[test] 441 | fn it_produces_single_value() { 442 | let value = try_join! { Some(1) }; 443 | assert_eq!(value.unwrap(), 1); 444 | } 445 | 446 | #[test] 447 | fn it_tests_step_expr_with_large_indices() { 448 | let value = try_join! { 449 | Some(1), 450 | Some(2) |> identity |> identity |> identity |> identity |> identity |> identity |> identity |> identity |> identity |> identity |> { 451 | // This step expression previously had the name collision with the twelfth branch's step expression 452 | let a = 5; 453 | 454 | move |val| val - a 455 | }, 456 | // Just a very long chain with step expressions 457 | Some(3) |> { let a = 1; move |v| v + a } |> { let a = 2; move |v| v + a } |> { let a = 3; move |v| v + a } |> { let a = 4; move |v| v + a } |> { let a = 5; move |v| v + a } |> { let a = 6; move |v| v + a } |> { let a = 7; move |v| v + a } |> { let a = 8; move |v| v + a } |> { let a = 9; move |v| v + a } |> { let a = 10; move |v| v + a } |> { let a = 11; move |v| v + a } |> { let a = 12; move |v| v + a } |> { let a = 13; move |v| v + a } |> { let a = 14; move |v| v + a } |> { let a = 15; move |v| v + a } |> { let a = 16; move |v| v + a } |> { let a = 17; move |v| v + a } |> { let a = 18; move |v| v + a } |> { let a = 19; move |v| v + a } |> { let a = 20; move |v| v + a }, 458 | Some(4), 459 | Some(5), 460 | Some(6), 461 | Some(7), 462 | Some(8), 463 | Some(9), 464 | Some(10), 465 | Some(11), 466 | Some(12) |> { 467 | let b = 1; 468 | 469 | move |val| val + b 470 | }, 471 | map => |_, second, third, _, _, _, _, _, _, _, _, twelfth| { 472 | assert_eq!(second, -3); 473 | assert_eq!(third, (1..=20).sum::() + 3); 474 | assert_eq!(twelfth, 13); 475 | 476 | second + twelfth + third 477 | } 478 | }; 479 | assert_eq!(value.unwrap(), 223); 480 | } 481 | 482 | #[test] 483 | fn it_tests_filter() { 484 | let value = try_join! { vec![1,2,3,4].into_iter() ?> |&value| value % 2 == 0 -> Some }; 485 | assert_eq!(value.unwrap().collect::>(), vec![2, 4]); 486 | } 487 | 488 | #[test] 489 | fn it_tests_nested_macro_combinations() { 490 | use futures::{executor::block_on, future::*}; 491 | use join::*; 492 | 493 | block_on(async { 494 | let value = try_join! { 495 | try_join! { 496 | Ok::<_,u8>(2u32), 497 | Ok::<_,u8>(3u32), 498 | Ok::<_,u8>(4u32), 499 | try_join! { 500 | Ok::<_,u8>(6u32), 501 | join! { 502 | Ok::<_,u8>(8u32), 503 | Ok::<_,u8>(9) ~|> |v| v + 1 504 | }.1, 505 | map => |a, b| b - a // 4 506 | }, 507 | map => |a, b, c, d| a + b + c + d // 13 508 | }, 509 | try_join!{ 510 | try_join_async! { 511 | ok::<_,u8>(21u32), 512 | ok::<_,u8>(22u32), 513 | ok::<_,u8>(23u32), 514 | map => |a, b, c| a * b * c // 10626 515 | }.await, 516 | Ok(2u32), 517 | and_then => |a, b| Ok(a * b) // 21252 518 | }, 519 | map => |a, b| a + b // 21265 520 | } 521 | .unwrap(); 522 | 523 | assert_eq!(value, 21265); 524 | }); 525 | } 526 | 527 | #[test] 528 | fn it_tests_initial_block_capture() { 529 | use std::sync::Arc; 530 | let out = Arc::new(5); 531 | let value = try_join! { 532 | let pat_1 = { let out = out.clone(); Some(*out) }, 533 | let pat_2 = { Some(*out) }, 534 | map => |a, _| a 535 | } 536 | .unwrap(); 537 | 538 | assert_eq!(value, 5); 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /join/tests/join_spawn.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[allow(clippy::unused_unit)] 3 | mod join_spawn_tests { 4 | use join::{join_spawn, try_join_spawn}; 5 | use std::error::Error; 6 | 7 | type BoxedError = Box; 8 | 9 | type Result = std::result::Result; 10 | 11 | type _Result = std::result::Result; 12 | 13 | fn three() -> u16 { 14 | 3 15 | } 16 | 17 | #[allow(clippy::unnecessary_wraps)] 18 | fn ok_four() -> Result { 19 | Ok(4) 20 | } 21 | 22 | #[allow(clippy::unnecessary_wraps)] 23 | fn some_five() -> Option { 24 | Some(5) 25 | } 26 | 27 | fn make_err() -> Result { 28 | Err("error".into()) 29 | } 30 | 31 | fn none() -> Option { 32 | None 33 | } 34 | 35 | fn add_one(v: u16) -> u16 { 36 | v + 1 37 | } 38 | 39 | #[allow(clippy::unnecessary_wraps)] 40 | fn add_one_ok(v: u16) -> Result { 41 | Ok(add_one(v)) 42 | } 43 | 44 | fn to_err(v: u16) -> Result { 45 | Err(v.to_string().into()) 46 | } 47 | 48 | fn to_none(_: u16) -> Option { 49 | None 50 | } 51 | 52 | #[test] 53 | fn it_produces_n_branches_with_length_1() { 54 | let product = try_join_spawn! { 55 | Ok(2u16), 56 | Ok(three()), 57 | ok_four(), 58 | some_five().ok_or_else(|| "error".into()), 59 | map => |a, b, c, d| a * b * c * d 60 | }; 61 | 62 | assert_eq!(product.unwrap(), 120); 63 | 64 | let err = try_join_spawn! { 65 | Ok(2), 66 | Ok(three()), 67 | ok_four(), 68 | make_err(), 69 | map => |a, b, c, d| a * b * c * d 70 | }; 71 | 72 | assert_eq!( 73 | err.unwrap_err().to_string(), 74 | make_err().unwrap_err().to_string() 75 | ); 76 | 77 | let product = try_join_spawn! { 78 | Some(2), 79 | Some(three()), 80 | some_five(), 81 | ok_four().ok(), 82 | map => |a, b, c, d| a * b * c * d 83 | }; 84 | 85 | assert_eq!(product, Some(120)); 86 | 87 | let none = try_join_spawn! { 88 | Some(2), 89 | Some(three()), 90 | some_five(), 91 | none(), 92 | ok_four().ok(), 93 | map => |a, b, c, d, e| a * b * c * d * e 94 | }; 95 | 96 | assert_eq!(none, None); 97 | } 98 | 99 | #[test] 100 | fn it_produces_n_branches_with_any_length() { 101 | let product = try_join_spawn! { 102 | Ok(2u16).map(add_one).and_then(add_one_ok), //4 103 | Ok(three()).and_then(add_one_ok).map(add_one).map(add_one).map(add_one), //7 104 | ok_four().map(add_one), //5 105 | some_five().map(add_one).ok_or_else(|| "error".into()).and_then(to_err).and_then(add_one_ok).or(Ok(5)), // 5 106 | map => |a, b, c, d| a * b * c * d 107 | }; 108 | 109 | assert_eq!(product.unwrap(), 700); 110 | 111 | let err = try_join_spawn! { 112 | Ok(2).map(add_one), 113 | Ok(three()).and_then(to_err), 114 | ok_four().and_then(|_| -> Result { Err("err".into()) }), 115 | make_err(), 116 | map => |a, b, c, d| a * b * c * d 117 | }; 118 | 119 | assert_eq!( 120 | err.unwrap_err().to_string(), 121 | to_err(three()).unwrap_err().to_string() 122 | ); 123 | } 124 | 125 | #[test] 126 | fn it_produces_n_branches_with_any_length_using_combinators() { 127 | let product = try_join_spawn! { 128 | Ok(2u16) |> add_one => add_one_ok, //4 129 | Ok(three()) => add_one_ok |> add_one |> add_one |> add_one, //7 130 | ok_four() |> add_one, //5 131 | some_five() |> add_one ..ok_or_else(|| "error".into()) => to_err => add_one_ok <| Ok(5), // 5 132 | map => |a, b, c, d| a * b * c * d 133 | }; 134 | 135 | assert_eq!(product.unwrap(), 700); 136 | 137 | let sum = try_join_spawn! { 138 | 2u16 -> Ok |> add_one => add_one_ok, //4 139 | three() -> Ok => add_one_ok |> add_one |> add_one |> add_one, //7 140 | ok_four() |> add_one, //5 141 | some_five() |> add_one ..ok_or_else(|| "error".into()) => to_err => add_one_ok <| Ok(5), // 5 142 | and_then => |a, b, c, d| Ok(a + b + c + d) 143 | }; 144 | 145 | assert_eq!(sum.unwrap(), 21); 146 | 147 | let err = try_join_spawn! { 148 | Ok(2) |> add_one, 149 | Ok(three()) => to_err, 150 | ok_four() => |_| -> Result { Err("error".into()) }, 151 | make_err(), 152 | map => |a, b, c, d| a * b * c * d 153 | }; 154 | 155 | assert_eq!( 156 | err.unwrap_err().to_string(), 157 | to_err(three()).unwrap_err().to_string() 158 | ); 159 | 160 | let none = try_join_spawn! { 161 | 2 -> Some |> add_one, 162 | Some(three()) => to_none, 163 | ok_four() => |_| -> Result { Ok(10) } ..ok(), 164 | none(), 165 | map => |a, b, c, d| a * b * c * d 166 | }; 167 | 168 | assert_eq!(none, None); 169 | } 170 | 171 | #[test] 172 | fn it_tests_handler_behaviour() { 173 | let ok = try_join_spawn! { 174 | Ok(2), 175 | Ok(3), 176 | ok_four(), 177 | and_then => |_a, _b, _c| Ok::, _>(None) 178 | }; 179 | 180 | assert_eq!(ok.unwrap(), None); 181 | 182 | let err = try_join_spawn! { 183 | Ok(2), 184 | Ok(3), 185 | ok_four(), 186 | and_then => |a: u8, _b, _c| Err::, _>(a.to_string().into()) 187 | }; 188 | 189 | assert_eq!( 190 | err.unwrap_err().to_string(), 191 | Err::("2".into()).unwrap_err().to_string() 192 | ); 193 | 194 | let some = try_join_spawn! { 195 | Some(2), 196 | Some(3), 197 | some_five(), 198 | and_then => |a, _b, _c| Some(a) 199 | }; 200 | 201 | assert_eq!(some, Some(2)); 202 | 203 | let none = try_join_spawn! { 204 | Some(2), 205 | Some(3), 206 | some_five(), 207 | and_then => |_a, _b, _c| None:: 208 | }; 209 | 210 | assert_eq!(none, None); 211 | 212 | let some = try_join_spawn! { 213 | Some(2u16), 214 | Some(3u16), 215 | some_five(), 216 | map => |a, b, c| a + b + c 217 | }; 218 | 219 | assert_eq!(some, Some(10)); 220 | 221 | let none: Option = try_join_spawn! { 222 | Some(2u16), 223 | None, 224 | some_five(), 225 | and_then => |a: u16, b: u16, c: u16| -> Option { Some(a + b + c) } 226 | }; 227 | 228 | assert_eq!(none, None); 229 | 230 | let ok = join_spawn! { 231 | Ok(2), 232 | Ok(3), 233 | ok_four(), 234 | then => |a: Result, b: Result, c: Result| Ok::<_, u8>(a.unwrap() + b.unwrap() + c.unwrap()) 235 | }; 236 | 237 | assert_eq!(ok, Ok(9)); 238 | 239 | let err = join_spawn! { 240 | Ok(2), 241 | Ok(3), 242 | ok_four(), 243 | then => |a: Result, b: Result, c: Result| Err::(a.unwrap() + b.unwrap() + c.unwrap()) 244 | }; 245 | 246 | assert_eq!(err, Err(9)); 247 | } 248 | 249 | #[test] 250 | fn it_tests_nested_macro_combinations() { 251 | use join::*; 252 | 253 | let value = try_join_spawn! { 254 | try_join_spawn! { 255 | Ok::<_,u8>(2u32), 256 | Ok::<_,u8>(3u32), 257 | Ok::<_,u8>(4u32), 258 | try_join_spawn! { 259 | Ok::<_,u8>(6u32), 260 | join_spawn! { 261 | Ok::<_,u8>(8u32), 262 | Ok::<_,u8>(9) ~|> |v| v + 1 263 | }.1, 264 | map => |a, b| b - a // 4 265 | }, 266 | map => |a, b, c, d| a + b + c + d // 13 267 | }, 268 | try_join_spawn!{ 269 | try_join! { 270 | Ok::<_,u8>(21u32), 271 | Ok::<_,u8>(22u32), 272 | Ok::<_,u8>(23u32), 273 | map => |a, b, c| a * b * c // 10626 274 | }, 275 | Ok(2u32), 276 | and_then => |a, b| Ok(a * b) // 21252 277 | }, 278 | map => |a, b| a + b // 21265 279 | } 280 | .unwrap(); 281 | 282 | assert_eq!(value, 21265); 283 | } 284 | 285 | #[test] 286 | fn it_tests_steps() { 287 | let product = try_join_spawn! { 288 | let branch_0 = Ok(2u16) ~|> { 289 | let branch_0 = branch_0.as_ref().ok().cloned(); 290 | let branch_1 = branch_1.as_ref().ok().cloned(); 291 | let branch_2 = branch_2.as_ref().ok().cloned(); 292 | let branch_3 = branch_3.as_ref().ok().cloned(); 293 | 294 | move |value| { 295 | assert_eq!(branch_0.unwrap(), value); 296 | assert_eq!(branch_1.unwrap(), 3); 297 | assert_eq!(branch_2.unwrap(), 4); 298 | assert_eq!(branch_3.unwrap(), 6); 299 | add_one(value) 300 | } 301 | } ~=> add_one_ok, //4 302 | let branch_1 = Ok(three()) ~=> add_one_ok ~|> { 303 | let branch_0 = branch_0.as_ref().ok().cloned(); 304 | let branch_1 = branch_1.as_ref().ok().cloned(); 305 | let branch_2 = branch_2.as_ref().ok().cloned(); 306 | let branch_3 = branch_3.as_ref().ok().cloned(); 307 | 308 | move |value| { 309 | assert_eq!(branch_0.unwrap(), 3); 310 | assert_eq!(branch_1.unwrap(), value); 311 | assert_eq!(branch_2.unwrap(), 5); 312 | assert_eq!(branch_3.unwrap(), 5); 313 | add_one(value) 314 | } 315 | } ~|> add_one ~|> add_one, //7 316 | let branch_2 = ok_four() ~|> add_one, //5 317 | let branch_3 = some_five() |> add_one ..ok_or_else(|| "error".into()) ~=> to_err <| Ok(5) ~=> add_one_ok, // 6 318 | map => |a, b, c, d| a * b * c * d 319 | }; 320 | 321 | assert_eq!(product.unwrap(), 840); 322 | } 323 | 324 | #[test] 325 | fn it_checks_multi_threading() { 326 | use std::{ 327 | sync::{Arc, Mutex}, 328 | thread, 329 | time::Duration, 330 | }; 331 | 332 | let values = Arc::new(Mutex::new(Vec::new())); 333 | 334 | let (values0, values1, values2) = (values.clone(), values.clone(), values.clone()); 335 | 336 | join_spawn! { 337 | Ok::<_,u8>((values0, 1)) |> |(values, value)| { 338 | values.lock().unwrap().push(value); 339 | thread::sleep(Duration::from_secs(1)); 340 | { 341 | let mut values = values.lock().unwrap(); 342 | values.sort_unstable(); 343 | assert_eq!(values[..], [1, 2, 3]); 344 | values.pop(); 345 | } 346 | (values, value + 1) 347 | } ~|> |(values, value)| { 348 | values.lock().unwrap().push(value); 349 | thread::sleep(Duration::from_secs(1)); 350 | { 351 | let mut values = values.lock().unwrap(); 352 | values.sort_unstable(); 353 | assert_eq!(values[..], [2, 3, 4]); 354 | } 355 | }, 356 | Ok::<_,u8>((values1, 2)) |> |(values, value)| { 357 | values.lock().unwrap().push(value); 358 | thread::sleep(Duration::from_secs(2)); 359 | { 360 | let mut values = values.lock().unwrap(); 361 | values.sort_unstable(); 362 | assert_eq!(values[..], [1, 2]); 363 | values.pop(); 364 | } 365 | (values, value + 1) 366 | } ~|> |(values, value)| { 367 | values.lock().unwrap().push(value); 368 | thread::sleep(Duration::from_secs(2)); 369 | { 370 | let mut values = values.lock().unwrap(); 371 | values.sort_unstable(); 372 | assert_eq!(values[..], [2, 3, 4]); 373 | } 374 | }, 375 | Ok::<_,u8>((values2, 3)) |> |(values, value)| { 376 | values.lock().unwrap().push(value); 377 | thread::sleep(Duration::from_secs(3)); 378 | { 379 | let mut values = values.lock().unwrap(); 380 | values.sort_unstable(); 381 | assert_eq!(values[..], [1]); 382 | values.pop(); 383 | } 384 | (values, value + 1) 385 | } ~|> |(values, value)| { 386 | values.lock().unwrap().push(value); 387 | thread::sleep(Duration::from_secs(3)); 388 | { 389 | let mut values = values.lock().unwrap(); 390 | values.sort_unstable(); 391 | assert_eq!(values[..], [2, 3, 4]); 392 | } 393 | }, 394 | then => move |_, _, _| { 395 | assert_eq!(values.lock().unwrap()[..], [2, 3, 4]); 396 | Ok::<_,u8>(()) 397 | } 398 | } 399 | .unwrap(); 400 | } 401 | 402 | #[test] 403 | fn it_checks_multi_threading_steps() { 404 | let product = try_join_spawn! { 405 | let branch_0 = Ok::<_, BoxedError>(2u16) ~|> { 406 | let branch_0 = branch_0.as_ref().ok().cloned(); 407 | let branch_1 = branch_1.as_ref().ok().cloned(); 408 | let branch_2 = branch_2.as_ref().ok().cloned(); 409 | let branch_3 = branch_3.as_ref().ok().cloned(); 410 | move |value| { 411 | assert_eq!(branch_0, Some(value)); 412 | assert_eq!(branch_1, Some(3)); 413 | assert_eq!(branch_2, Some(4)); 414 | assert_eq!(branch_3, Some(6)); 415 | add_one(value) 416 | } 417 | } ~=> add_one_ok, //4 418 | let branch_1 = Ok::<_, BoxedError>(three()) ~=> add_one_ok ~|> |value| value |> { 419 | let branch_0 = branch_0.as_ref().ok().cloned(); 420 | let branch_1 = branch_1.as_ref().ok().cloned(); 421 | let branch_2 = branch_2.as_ref().ok().cloned(); 422 | let branch_3 = branch_3.as_ref().ok().cloned(); 423 | move |value| { 424 | assert_eq!(branch_0, Some(3)); 425 | assert_eq!(branch_1, Some(value)); 426 | assert_eq!(branch_2, Some(5)); 427 | assert_eq!(branch_3, Some(5)); 428 | add_one(value) 429 | } 430 | } ~|> add_one ~|> add_one, //7 431 | let branch_2 = Ok::<_, BoxedError>(4u16) ~|> add_one, //5 432 | let branch_3 = some_five() |> add_one ..ok_or_else(|| "error".into()) ~=> |_| Err("".into()) <| Ok(5) ~=> add_one_ok, // 6 433 | map => |a, b, c, d| a * b * c * d 434 | }; 435 | 436 | assert_eq!(product.unwrap(), 840); 437 | } 438 | 439 | #[test] 440 | fn it_produces_tuple() { 441 | let values = try_join_spawn! { Ok::<_,u8>(2), Ok::<_,u8>(3) }; 442 | assert_eq!(values.unwrap(), (2, 3)); 443 | } 444 | 445 | #[test] 446 | fn it_produces_single_value() { 447 | let value = try_join_spawn! { Some(1) }; 448 | assert_eq!(value.unwrap(), 1); 449 | } 450 | 451 | #[test] 452 | fn it_checks_evalutation_in_case_of_error() { 453 | let error = try_join_spawn! { Err::(2) ~|> |_| unreachable!(), Ok::<_,u8>(3) }; 454 | assert_eq!(error, Err(2)); 455 | } 456 | 457 | #[test] 458 | fn it_tests_multi_step_single_branch() { 459 | let values = try_join_spawn! { vec![1,2,3,4,5,6,7,8,9].into_iter() -> Some ~|> >>> ?> |v| v % 3 != 0 =>[] Vec<_> }.unwrap(); 460 | assert_eq!(values, vec![1, 2, 4, 5, 7, 8]); 461 | } 462 | 463 | #[test] 464 | fn it_tests_iter_combinators() { 465 | let mut some_vec = Some(vec![0u8]); 466 | 467 | let values: (Vec<_>, Vec<_>) = join_spawn! { 468 | vec![2u8, 3, 4, 5, 6, 7, 8, 9, 10, 11].into_iter() |> |v| { some_vec = None; v + 1 } ?|> |v| if v % 2 == 0 { Some(v) } else { None } |n> ^@ { some_vec.clone() }, |mut acc, (index, v)| { acc.as_mut().unwrap().push(v + (index as u8)); acc } ..unwrap().into_iter() =>[] Vec<_> ..into_iter() ?&!> |&n| (n as f64).cos().abs() > ::std::f64::consts::PI / 3f64 469 | }; 470 | 471 | assert_eq!(values, (vec![], vec![0, 4, 7, 10, 13, 16])); 472 | 473 | let values = vec![0, 1u8, 2, 3, 4, 5, 6]; 474 | let other_values = vec![4u8, 5, 6, 7, 8, 9, 10]; 475 | 476 | assert_eq!( 477 | join_spawn! { 478 | { let values = values.clone(); values.into_iter() } >^> { let other_values = other_values.clone(); other_values.into_iter() } ?&!> |(v, v1)| v % 2 == 0 && v1 % 2 == 0, 479 | }, 480 | ( 481 | vec![(0u8, 4u8), (2, 6), (4, 8), (6, 10)], 482 | vec![(1u8, 5u8), (3, 7), (5, 9)] 483 | ) 484 | ); 485 | 486 | assert_eq!( 487 | join_spawn! { 488 | { let values = values.clone(); values.into_iter() } >^> { let other_values = other_values.clone(); other_values.into_iter() } <-> _, _, Vec<_>, Vec<_> 489 | }, 490 | (values, other_values) 491 | ); 492 | 493 | assert_eq!( 494 | join_spawn! { vec![1u8, 2, 3, 4, 5].into_iter() ?> |v| v % 2 != 0 =>[] Vec<_> }, 495 | vec![1u8, 3, 5] 496 | ); 497 | 498 | assert_eq!( 499 | try_join_spawn! { let v = vec![1, 2, 3, 4, 5] -> Some ~=> >>> ..into_iter() ?|>@ |v| if v % 2 == 0 { Some(v) } else { None } }, 500 | Some(2) 501 | ); 502 | 503 | assert_eq!( 504 | join_spawn! { vec![vec![1, 2, 3], vec![2]].into_iter() ^^> =>[] Vec<_> }, 505 | vec![1, 2, 3, 2] 506 | ); 507 | 508 | assert!( 509 | try_join_spawn! { vec![Ok(5), Err(4)].into_iter() ?^@ 0, |acc, v| v.map(|v| acc + v) } 510 | .is_err() 511 | ); 512 | } 513 | 514 | #[test] 515 | fn it_tests_initial_block_capture() { 516 | use std::sync::Arc; 517 | let out = Arc::new(5); 518 | 519 | let value = try_join_spawn! { 520 | let pat_1 = { let out = out.clone(); Some(*out) }, 521 | let pat_2 = { Some(*out) }, 522 | map => |a, _| a 523 | } 524 | .unwrap(); 525 | 526 | assert_eq!(value, 5); 527 | } 528 | } 529 | -------------------------------------------------------------------------------- /join/tests/join_async_spawn.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[allow(clippy::unused_unit)] 3 | mod join_async_spawn_tests { 4 | use futures::future::{err, ok, ready}; 5 | use futures_timer::Delay; 6 | use join::{join_async_spawn, try_join_async_spawn}; 7 | use std::{error::Error, time::Duration}; 8 | use tokio::runtime::Runtime; 9 | 10 | type BoxedError = Box; 11 | 12 | type Result = std::result::Result; 13 | 14 | type _Result = std::result::Result; 15 | 16 | async fn three() -> u16 { 17 | 3 18 | } 19 | 20 | #[allow(clippy::unnecessary_wraps)] 21 | async fn ok_four() -> Result { 22 | Ok(4) 23 | } 24 | 25 | #[allow(clippy::unnecessary_wraps)] 26 | async fn ok_five() -> Result { 27 | Ok(5) 28 | } 29 | 30 | async fn make_err() -> Result { 31 | Err("error".into()) 32 | } 33 | 34 | fn add_one_sync(v: u16) -> u16 { 35 | v + 1 36 | } 37 | 38 | async fn add_one(v: u16) -> u16 { 39 | v + 1 40 | } 41 | 42 | async fn add_one_ok(v: u16) -> Result { 43 | Ok(add_one(v).await) 44 | } 45 | 46 | async fn to_err(v: u16) -> Result { 47 | Err(v.to_string().into()) 48 | } 49 | 50 | fn add_one_to_ok(v: Result) -> Result { 51 | v.map(add_one_sync) 52 | } 53 | 54 | #[test] 55 | fn it_produces_n_branches_with_length_1() { 56 | let rt = Runtime::new().unwrap(); 57 | rt.block_on(async { 58 | let product = try_join_async_spawn! { 59 | ok(2u16), 60 | ok(three().await), 61 | 62 | ok_four(), 63 | ok(ok_five().await.unwrap()), 64 | and_then => |a, b, c, d| ok(a * b * c * d) 65 | }; 66 | 67 | assert_eq!(product.await.unwrap(), 120u16); 68 | 69 | let err = try_join_async_spawn! { 70 | ok(2u16), 71 | ok(three().await), 72 | ok_four(), 73 | make_err(), 74 | and_then => |a, b, c, d| ok(a * b * c * d) 75 | }; 76 | 77 | assert_eq!( 78 | format!("{:?}", err.await.unwrap_err()), 79 | format!("{:?}", make_err().await.unwrap_err()) 80 | ); 81 | }); 82 | } 83 | 84 | #[test] 85 | fn it_produces_n_branches_with_any_length() { 86 | let rt = Runtime::new().unwrap(); 87 | rt.block_on(async { 88 | let product = try_join_async_spawn! { 89 | ok(2u16).map(add_one_to_ok).and_then(add_one_ok), //4 90 | ok(three().await).and_then(add_one_ok).map(add_one_to_ok).map(add_one_to_ok).map(add_one_to_ok), //7 91 | ok_four().map(add_one_to_ok), //5 92 | ok_five().and_then(to_err).and_then(add_one_ok).or_else(|_| ok(5)), // 5 93 | and_then => |a, b, c, d| ok(a * b * c * d) 94 | }; 95 | 96 | assert_eq!(product.await.unwrap(), 700u16); 97 | 98 | let err = try_join_async_spawn! { 99 | ok(2).map(add_one_to_ok), 100 | ok(three().await).and_then(to_err), 101 | ok_four(), 102 | ok(2) ~=> |_| make_err(), 103 | map => |a, b, c, d| a * b * c * d 104 | }; 105 | 106 | assert_eq!( 107 | format!("{:?}", err.await.unwrap_err()), 108 | format!("{:?}", to_err(three().await).await.unwrap_err()) 109 | ); 110 | }); 111 | } 112 | 113 | #[test] 114 | fn it_produces_n_branches_with_any_length_using_combinators() { 115 | let rt = Runtime::new().unwrap(); 116 | rt.block_on(async { 117 | let product = try_join_async_spawn! { 118 | ok(2u16) |> add_one_to_ok => add_one_ok, //4 119 | ok(three().await) => add_one_ok |> add_one_to_ok |> add_one_to_ok |> add_one_to_ok, //7 120 | ok_four() |> add_one_to_ok, //5 121 | ok_five() |> add_one_to_ok => to_err => add_one_ok <= |_| ok(5), // 5 122 | map => |a, b, c, d| a * b * c * d 123 | }; 124 | 125 | assert_eq!(product.await.unwrap(), 700); 126 | 127 | let sum = try_join_async_spawn! { 128 | 2u16 -> ok |> add_one_to_ok => add_one_ok, //4 129 | three().await -> ok => add_one_ok |> add_one_to_ok |> add_one_to_ok |> add_one_to_ok, //7 130 | ok_four() |> add_one_to_ok, //5 131 | ok_five() |> add_one_to_ok => to_err => add_one_ok <= |_| ok(5), // 5 132 | and_then => |a, b, c, d| ok(a + b + c + d) 133 | }; 134 | 135 | assert_eq!(sum.await.unwrap(), 21); 136 | 137 | let error: Result = try_join_async_spawn! { 138 | ok(2) |> add_one_to_ok, 139 | ok(three().await) => to_err, 140 | ok_four() => |_| err("some error".into()), 141 | make_err(), 142 | map => |a: u16, b: u16, c: u16, d: u16| a * b * c * d 143 | } 144 | .await; 145 | 146 | assert!(error.is_err()); 147 | 148 | let error = try_join_async_spawn! { 149 | 2 -> ok, 150 | ok_five() ~=> |_| err("some error".into()), 151 | ok_four(), 152 | make_err(), 153 | map => |a: u16, b: u16, c: u16, d: u16| a * b * c * d 154 | }; 155 | 156 | assert_eq!( 157 | format!("{:#?}", error.await.unwrap_err()), 158 | format!("{:#?}", Box::new("error")) 159 | ); 160 | }); 161 | } 162 | 163 | #[allow(unreachable_code)] 164 | #[test] 165 | fn it_checks_evalutation_in_case_of_error() { 166 | let rt = Runtime::new().unwrap(); 167 | rt.block_on(async { 168 | let error = 169 | try_join_async_spawn! { err::(2u8) ~=> |_| { unreachable!(); ok(2u8) }, ok::(3u8) }; 170 | assert_eq!(error.await, Err(2u8)); 171 | }); 172 | } 173 | 174 | #[test] 175 | fn it_tests_handler_behaviour() { 176 | let rt = Runtime::new().unwrap(); 177 | rt.block_on( 178 | async { 179 | let ok_value = try_join_async_spawn! { 180 | ok(2u16), 181 | ok(3u16), 182 | ok_four(), 183 | and_then => |_a, _b, _c| ok::, _>(None) 184 | }; 185 | 186 | assert_eq!(ok_value.await.unwrap(), None); 187 | 188 | let err_value = try_join_async_spawn! { 189 | ok(2u16), 190 | ok(3u16), 191 | ok_four(), 192 | and_then => |a, _b, _c| to_err(a) 193 | }; 194 | 195 | assert_eq!(format!("{:?}", err_value.await.unwrap_err()), format!("{:?}", to_err(2).await.unwrap_err())); 196 | 197 | let some = try_join_async_spawn! { 198 | ready(Ok(2u16)), 199 | ready(Ok(3u16)), 200 | ok_five(), 201 | map => |a, _b, _c| a 202 | }; 203 | 204 | assert_eq!(some.await.unwrap(), 2u16); 205 | 206 | let okay: Result = join_async_spawn! { 207 | ready(Ok::<_,BoxedError>(2u16)), 208 | ready(Ok::<_,BoxedError>(3u16)), 209 | err::("25".into()), 210 | then => |a, _b, _c| ready(a) 211 | }.await; 212 | 213 | assert!(okay.is_ok()); 214 | 215 | let okay = try_join_async_spawn! { 216 | ready(Ok(2u16)), 217 | ready(Ok(3u16)), 218 | ok_five(), 219 | map => |a, b, c| a + b + c 220 | }; 221 | 222 | assert_eq!(okay.await.unwrap(), 10); 223 | 224 | let ok_value= join_async_spawn! { 225 | ok(2u16), 226 | ok(3u16), 227 | ok_four(), 228 | then => |a: Result, b: Result, c: Result| ok::<_, u8>(a.unwrap() + b.unwrap() + c.unwrap()) 229 | }; 230 | 231 | assert_eq!(ok_value.await.unwrap(), 9u16); 232 | 233 | let err_value = join_async_spawn! { 234 | ok(2u16), 235 | ok(3u16), 236 | ok_four(), 237 | then => |a: Result, b: Result, c: Result| err::(a.unwrap() + b.unwrap() + c.unwrap()) 238 | }; 239 | 240 | assert_eq!(err_value.await, Err(9)); 241 | } 242 | ); 243 | } 244 | 245 | #[test] 246 | fn it_tests_steps() { 247 | let rt = Runtime::new().unwrap(); 248 | rt.block_on(async { 249 | let product = try_join_async_spawn! { 250 | let branch_0 = ok(2u16) ~|> { 251 | let branch_0 = branch_0.as_ref().ok().cloned(); 252 | let branch_1 = branch_1.as_ref().ok().cloned(); 253 | let branch_2 = branch_2.as_ref().ok().cloned(); 254 | let branch_3 = branch_3.as_ref().ok().cloned(); 255 | move |value: Result| { 256 | assert_eq!(branch_0, value.as_ref().ok().cloned()); 257 | assert_eq!(branch_1, Some(3)); 258 | assert_eq!(branch_2, Some(4)); 259 | assert_eq!(branch_3, Some(5)); 260 | value.map(add_one_sync) 261 | } 262 | } ~=> add_one_ok, //4 263 | let branch_1 = three().await -> ok ~=> add_one_ok ~|> |value| value |> { 264 | let branch_0 = branch_0.as_ref().ok().cloned(); 265 | let branch_1 = branch_1.as_ref().ok().cloned(); 266 | let branch_2 = branch_2.as_ref().ok().cloned(); 267 | let branch_3 = branch_3.as_ref().ok().cloned(); 268 | move |value: Result| { 269 | assert_eq!(branch_0, Some(3)); 270 | assert_eq!(branch_1, value.as_ref().ok().cloned()); 271 | assert_eq!(branch_2, Some(5)); 272 | assert_eq!(branch_3, Some(6)); 273 | value.map(add_one_sync) 274 | } 275 | } ~|> add_one_to_ok ~|> add_one_to_ok, //7 276 | let branch_2 = ok_four() ~|> add_one_to_ok, //5 277 | let branch_3 = ok_five() ~|> add_one_to_ok ~=> to_err <= |_| ok(5) ~=> add_one_ok, // 6 278 | map => |a, b, c, d| a * b * c * d 279 | }; 280 | 281 | assert_eq!(product.await.unwrap(), 840); 282 | }); 283 | } 284 | 285 | #[test] 286 | fn it_tests_nested_macro_combinations() { 287 | use join::*; 288 | let rt = Runtime::new().unwrap(); 289 | 290 | rt.block_on(async { 291 | let value = try_join_async! { 292 | try_join_async_spawn! { 293 | ok::<_,()>(2u32), 294 | ok::<_,()>(3u32), 295 | ok::<_,()>(4u32), 296 | ok::<_,()>(4u32), 297 | map => |a, b, c, d| a + b + c + d // 13 298 | }, 299 | try_join_async!{ 300 | try_join_async! { 301 | ok::<_,()>(21u32), 302 | ok::<_,()>(22u32), 303 | ok::<_,()>(23u32), 304 | map => |a, b, c| a * b * c // 10626 305 | }, 306 | ok(2u32), 307 | and_then => |a, b| ok(a * b) // 21252 308 | }, 309 | map => |a, b| a + b // 21265 310 | } 311 | .await 312 | .unwrap(); 313 | 314 | assert_eq!(value, 21265); 315 | }); 316 | } 317 | 318 | #[test] 319 | fn it_checks_concurrent_branches_execution() { 320 | let rt = Runtime::new().unwrap(); 321 | rt.block_on(async { 322 | use futures::lock::Mutex; 323 | use std::sync::Arc; 324 | 325 | let values = Arc::new(Mutex::new(Vec::new())); 326 | 327 | join_async_spawn! { 328 | ok((values.clone(), 1u16)) => |(values, value)| async move { 329 | values.lock().await.push(value); 330 | Delay::new(Duration::from_secs(1)).await.unwrap(); 331 | { 332 | let mut values = values.lock().await; 333 | values.sort_unstable(); 334 | assert_eq!(values[..], [1, 2, 3]); 335 | values.pop(); 336 | } 337 | Ok::<_, BoxedError>((values, value + 1)) 338 | } ~=> |(values, value)| async move { 339 | values.lock().await.push(value); 340 | Delay::new(Duration::from_secs(1)).await.unwrap(); 341 | let mut values = values.lock().await; 342 | values.sort_unstable(); 343 | assert_eq!(values[..], [2, 3, 4]); 344 | Ok::<_, BoxedError>(()) 345 | }, 346 | ok((values.clone(), 2u16)) => |(values, value)| async move { 347 | values.lock().await.push(value); 348 | Delay::new(Duration::from_secs(2)).await.unwrap(); 349 | { 350 | let mut values = values.lock().await; 351 | values.sort_unstable(); 352 | assert_eq!(values[..], [1, 2]); 353 | values.pop(); 354 | } 355 | Ok::<_, BoxedError>((values, value + 1)) 356 | } ~=> |(values, value)| async move { 357 | values.lock().await.push(value); 358 | Delay::new(Duration::from_secs(2)).await.unwrap(); 359 | let mut values = values.lock().await; 360 | values.sort_unstable(); 361 | assert_eq!(values[..], [2, 3, 4]); 362 | Ok::<_, BoxedError>(()) 363 | }, 364 | ok((values.clone(), 3u16)) => |(values, value)| async move { 365 | values.lock().await.push(value); 366 | Delay::new(Duration::from_secs(3)).await.unwrap(); 367 | { 368 | let mut values = values.lock().await; 369 | values.sort_unstable(); 370 | assert_eq!(values[..], [1]); 371 | values.pop(); 372 | } 373 | Ok::<_, BoxedError>((values, value + 1)) 374 | } ~=> |(values, value)| async move { 375 | values.lock().await.push(value); 376 | Delay::new(Duration::from_secs(3)).await.unwrap(); 377 | let mut values = values.lock().await; 378 | values.sort_unstable(); 379 | assert_eq!(values[..], [2, 3, 4]); 380 | Ok::<_, BoxedError>(()) 381 | }, 382 | then => |_, _, _| async { 383 | assert_eq!(values.clone().lock().await[..], [2, 3, 4]); 384 | Ok::<_, BoxedError>(()) 385 | } 386 | } 387 | .await 388 | .unwrap(); 389 | }); 390 | } 391 | 392 | #[test] 393 | fn it_produces_tuple() { 394 | let rt = Runtime::new().unwrap(); 395 | rt.block_on(async { 396 | let values = try_join_async_spawn! { ok::<_,()>(2), ok::<_,()>(3) }.await; 397 | assert_eq!(values.unwrap(), (2, 3)); 398 | }); 399 | } 400 | 401 | #[test] 402 | fn it_produces_single_value() { 403 | let rt = Runtime::new().unwrap(); 404 | rt.block_on(async { 405 | let value = try_join_async_spawn! { ready(Ok::<_,()>(1)) }.await; 406 | assert_eq!(value.unwrap(), 1); 407 | }); 408 | } 409 | 410 | #[allow(unreachable_code)] 411 | #[test] 412 | fn it_creates_unpolled_future() { 413 | let rt = Runtime::new().unwrap(); 414 | rt.block_on(async { 415 | let _fut = join_async_spawn! { 416 | ready(panic!()), 417 | ready(unreachable!()), 418 | then => |a: Result, _b: Result| ready(a) 419 | }; 420 | }); 421 | } 422 | 423 | #[test] 424 | fn it_cant_work_without_tokio() { 425 | assert!( 426 | ::std::panic::catch_unwind(|| ::futures::executor::block_on(async { 427 | let failure = try_join_async_spawn! { ok::<_,()>(2u16), ok::<_,()>(1u16) }; 428 | failure.await 429 | })) 430 | .is_err() 431 | ); 432 | } 433 | 434 | #[test] 435 | fn it_tests_multi_step_single_branch() { 436 | let rt = Runtime::new().unwrap(); 437 | rt.block_on(async { 438 | let values = try_join_async_spawn! { vec![1u8,2,3,4,5,6,7,8,9].into_iter() -> ok ~=> >>> ?> |v| v % 3 != 0 =>[] Vec<_> -> ok::<_,()> ~|> |v| v ~=> ok }.await.unwrap(); 439 | assert_eq!(values, vec![1u8, 2, 4, 5, 7, 8]); 440 | }); 441 | } 442 | 443 | #[test] 444 | fn it_tests_iter_combinators() { 445 | let rt = Runtime::new().unwrap(); 446 | rt.block_on(async { 447 | let mut some_vec = Some(vec![0u8]); 448 | 449 | let values: (Vec, Vec) = join_async_spawn! { 450 | vec![2u8, 3, 4, 5, 6, 7, 8, 9, 10, 11].into_iter() |> |v| { some_vec = None; v + 1 } ?|> |v| if v % 2 == 0 { Some(v) } else { None } |n> ^@ { some_vec.clone() }, |mut acc, (index, v)| { acc.as_mut().unwrap().push(v + (index as u8)); acc } ..unwrap().into_iter() =>[] Vec<_> ..into_iter() ?&!> |&n| (n as f64).cos().abs() > ::std::f64::consts::PI / 3f64 -> ready 451 | }.await; 452 | 453 | assert_eq!(values, (vec![], vec![0u8, 4, 7, 10, 13, 16])); 454 | 455 | let values = vec![0, 1u8, 2, 3, 4, 5, 6]; 456 | let other_values = vec![4u8, 5, 6, 7, 8, 9, 10]; 457 | 458 | assert_eq!( 459 | join_async_spawn! { 460 | { let values = values.clone(); values.into_iter() } >^> { let other_values = other_values.clone(); other_values.into_iter() } ?&!> |(v, v1)| v % 2 == 0 && v1 % 2 == 0 -> ready, 461 | }.await, 462 | ( 463 | vec![(0u8, 4u8), (2, 6), (4, 8), (6, 10)], 464 | vec![(1u8, 5u8), (3, 7), (5, 9)] 465 | ) 466 | ); 467 | 468 | let values = vec![0, 1u8, 2, 3, 4, 5, 6]; 469 | let other_values = vec![4u8, 5, 6, 7, 8, 9, 10]; 470 | 471 | assert_eq!( 472 | join_async_spawn! { 473 | { let values = values.clone(); values.into_iter() } >^> { let other_values = other_values.clone(); other_values.into_iter() } <-> u8, u8, Vec<_>, Vec<_> -> ready 474 | }.await, 475 | { 476 | let values = vec![0, 1u8, 2, 3, 4, 5, 6]; 477 | let other_values = vec![4u8, 5, 6, 7, 8, 9, 10]; 478 | (values, other_values) 479 | } 480 | ); 481 | 482 | 483 | assert_eq!( 484 | join_async_spawn! { vec![1u8, 2, 3, 4, 5].into_iter() ?> |v| v % 2 != 0 =>[] Vec<_> -> ready }.await, 485 | vec![1u8, 3, 5] 486 | ); 487 | 488 | assert_eq!( 489 | try_join_async_spawn! { let v = vec![1u8, 2, 3, 4, 5] ..into_iter() ?|>@ |v: u8| if v % 2 == 0 { Some(v) } else { None } -> |v: Option| ready(v.ok_or(Err::("e"))) }.await, 490 | Ok(2u8) 491 | ); 492 | 493 | assert_eq!( 494 | join_async_spawn! { vec![vec![1u8, 2, 3], vec![2]].into_iter() ^^> =>[] Vec<_> -> ready }.await, 495 | vec![1u8, 2, 3, 2] 496 | ); 497 | 498 | assert!( 499 | try_join_async_spawn! { vec![Ok(5u8), Err(4u8)].into_iter() ?^@ 0u8, |acc, v| v.map(|v| acc + v) -> ready }.await.is_err() 500 | ); 501 | }); 502 | } 503 | 504 | #[test] 505 | fn it_tests_initial_block_capture() { 506 | use std::sync::Arc; 507 | 508 | let rt = Runtime::new().unwrap(); 509 | rt.block_on(async { 510 | let out = Arc::new(5); 511 | let value = try_join_async_spawn! { 512 | let pat_1 = { let out = out.clone(); ok::<_,()>(*out) }, 513 | let pat_2 = { ok::<_,()>(*out) }, 514 | map => |a, _| a 515 | } 516 | .await 517 | .unwrap(); 518 | 519 | assert_eq!(value, 5); 520 | }); 521 | } 522 | } 523 | -------------------------------------------------------------------------------- /join/tests/join_async.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[allow(clippy::unused_unit)] 3 | mod join_async_tests { 4 | use futures::{ 5 | executor::block_on, 6 | future::{err, ok, ready}, 7 | }; 8 | use futures_timer::Delay; 9 | use join::{join_async, try_join_async}; 10 | use std::error::Error; 11 | 12 | type BoxedError = Box; 13 | 14 | type Result = std::result::Result; 15 | 16 | type _Result = std::result::Result; 17 | 18 | async fn three() -> u16 { 19 | 3 20 | } 21 | 22 | #[allow(clippy::unnecessary_wraps)] 23 | async fn ok_four() -> Result { 24 | Ok(4) 25 | } 26 | 27 | #[allow(clippy::unnecessary_wraps)] 28 | async fn ok_five() -> Result { 29 | Ok(5) 30 | } 31 | 32 | async fn make_err() -> Result { 33 | Err("error".into()) 34 | } 35 | 36 | fn add_one_sync(v: u16) -> u16 { 37 | v + 1 38 | } 39 | 40 | async fn add_one(v: u16) -> u16 { 41 | v + 1 42 | } 43 | 44 | async fn add_one_ok(v: u16) -> Result { 45 | Ok(add_one(v).await) 46 | } 47 | 48 | async fn to_err(v: u16) -> Result { 49 | Err(v.to_string().into()) 50 | } 51 | 52 | fn add_one_to_ok(v: Result) -> Result { 53 | v.map(add_one_sync) 54 | } 55 | 56 | #[test] 57 | fn it_produces_n_branches_with_length_1() { 58 | block_on(async { 59 | let product = try_join_async! { 60 | ok(2u16), 61 | ok(three().await), 62 | ok_four(), 63 | ok(ok_five().await.unwrap()), 64 | and_then => |a, b, c, d| ok(a * b * c * d) 65 | }; 66 | 67 | assert_eq!(product.await.unwrap(), 120u16); 68 | 69 | let err = try_join_async! { 70 | ok(2u16), 71 | ok(three().await), 72 | ok_four(), 73 | make_err(), 74 | and_then => |a, b, c, d| ok(a * b * c * d) 75 | }; 76 | 77 | assert_eq!( 78 | format!("{:?}", err.await.unwrap_err()), 79 | format!("{:?}", make_err().await.unwrap_err()) 80 | ); 81 | }); 82 | } 83 | 84 | #[test] 85 | fn it_produces_n_branches_with_any_length() { 86 | block_on(async { 87 | let product = try_join_async! { 88 | ok(2u16).map(add_one_to_ok).and_then(add_one_ok), //4 89 | ok(three().await).and_then(add_one_ok).map(add_one_to_ok).map(add_one_to_ok).map(add_one_to_ok), //7 90 | ok_four().map(add_one_to_ok), //5 91 | ok_five().map(|v| v).and_then(to_err).and_then(add_one_ok).or_else(|_| ok(5)), // 5 92 | and_then => |a, b, c, d| ok(a * b * c * d) 93 | }; 94 | 95 | assert_eq!(product.await.unwrap(), 700u16); 96 | 97 | let err = try_join_async! { 98 | ok(2).map(add_one_to_ok), 99 | ok(three().await).and_then(to_err), 100 | ok_four(), 101 | make_err(), 102 | map => |a, b, c, d| a * b * c * d 103 | }; 104 | 105 | assert_eq!( 106 | format!("{:?}", err.await.unwrap_err()), 107 | format!("{:?}", to_err(three().await).await.unwrap_err()) 108 | ); 109 | }); 110 | } 111 | 112 | #[test] 113 | fn it_produces_n_branches_with_any_length_using_combinators() { 114 | block_on(async { 115 | let product = try_join_async! { 116 | ok(2u16) |> add_one_to_ok => add_one_ok, //4 117 | ok(three().await) => add_one_ok |> add_one_to_ok |> add_one_to_ok |> add_one_to_ok, //7 118 | ok_four() |> add_one_to_ok, //5 119 | ok_five() |> |v| v |> add_one_to_ok => to_err => add_one_ok <= |_| ok(5), // 5 120 | map => |a, b, c, d| a * b * c * d 121 | }; 122 | 123 | assert_eq!(product.await.unwrap(), 700); 124 | 125 | let sum = try_join_async! { 126 | 2u16 -> ok |> add_one_to_ok => add_one_ok, //4 127 | three().await -> ok => add_one_ok |> add_one_to_ok |> add_one_to_ok |> add_one_to_ok, //7 128 | ok_four() |> add_one_to_ok, //5 129 | ok_five() |> |v| v |> add_one_to_ok => to_err => add_one_ok <= |_| ok(5), // 5 130 | and_then => |a, b, c, d| ok(a + b + c + d) 131 | }; 132 | 133 | assert_eq!(sum.await.unwrap(), 21); 134 | 135 | let err: Result = try_join_async! { 136 | ok(2) |> add_one_to_ok, 137 | ok(three().await) => to_err, 138 | ok_four() => |_| err("some error".into()), 139 | make_err(), 140 | map => |a: u16, b: u16, c: u16, d: u16| a * b * c * d 141 | } 142 | .await; 143 | 144 | assert_eq!( 145 | format!("{:?}", err.unwrap_err()), 146 | format!("{:?}", to_err(three().await).await.unwrap_err()) 147 | ); 148 | }); 149 | } 150 | 151 | #[test] 152 | fn it_tests_handler_behaviour() { 153 | block_on(async { 154 | let ok_value = try_join_async! { 155 | ok(2u16), 156 | ok(3u16), 157 | ok_four(), 158 | and_then => |_a, _b, _c| ok::, _>(None) 159 | }; 160 | 161 | assert_eq!(ok_value.await.unwrap(), None); 162 | 163 | let err_value = try_join_async! { 164 | ok(2u16), 165 | ok(3u16), 166 | ok_four(), 167 | and_then => |a, _b, _c| to_err(a) 168 | }; 169 | 170 | assert_eq!( 171 | format!("{:?}", err_value.await.unwrap_err()), 172 | format!("{:?}", to_err(2).await.unwrap_err()) 173 | ); 174 | 175 | let okay = try_join_async! { 176 | ready(Ok(2u16)), 177 | ready(Ok(3u16)), 178 | ok_five(), 179 | map => |a, _b, _c| a 180 | }; 181 | 182 | assert_eq!(okay.await.unwrap(), 2u16); 183 | 184 | let error: Result = try_join_async! { 185 | ready(Ok(2u16)), 186 | ready(Ok(3u16)), 187 | ok_five(), 188 | and_then => |_a, _b, _c| ready(Err("error".into())) 189 | } 190 | .await; 191 | 192 | assert_eq!(error.unwrap_err().to_string(), "error".to_string()); 193 | 194 | let okay = try_join_async! { 195 | ready(Ok(2u16)), 196 | ready(Ok(3u16)), 197 | ok_five(), 198 | map => |a, b, c| a + b + c 199 | }; 200 | 201 | assert_eq!(okay.await.unwrap(), 10); 202 | 203 | let okay: Result = join_async! { 204 | ready(Ok::<_,BoxedError>(2u16)), 205 | ready(Ok::<_,BoxedError>(3u16)), 206 | err::("25".into()), 207 | then => |a, _b, _c| ready(a) 208 | } 209 | .await; 210 | 211 | assert!(okay.is_ok()); 212 | 213 | let error = try_join_async! { 214 | ready(Ok(2u16)), 215 | Err("hey".into()) -> ready, 216 | ok_five(), 217 | map => |a: u16, b: u16, c: u16| a + b + c 218 | }; 219 | 220 | assert_eq!(error.await.unwrap_err().to_string(), "hey".to_string()); 221 | 222 | let ok_value = join_async! { 223 | ok(2u16), 224 | ok(3u16), 225 | ok_four(), 226 | then => |a: Result, b: Result, c: Result| ok::<_,()>(a.unwrap() + b.unwrap() + c.unwrap()) 227 | }; 228 | 229 | assert_eq!(ok_value.await.unwrap(), 9u16); 230 | 231 | let err_value = join_async! { 232 | ok(2u16), 233 | ok(3u16), 234 | ok_four(), 235 | then => |a: Result, b: Result, c: Result| err::(a.unwrap() + b.unwrap() + c.unwrap()) 236 | }; 237 | 238 | assert_eq!(err_value.await, Err(9)); 239 | }); 240 | } 241 | 242 | #[allow(unreachable_code)] 243 | #[test] 244 | fn it_checks_evalutation_in_case_of_error() { 245 | block_on(async { 246 | let error = try_join_async! { err::(2u8) ~=> |_| { unreachable!(); ok(2u8) }, ok::(3u8) }; 247 | assert_eq!(error.await, Err(2u8)); 248 | }); 249 | } 250 | 251 | #[test] 252 | fn it_tests_steps() { 253 | block_on(async { 254 | let product = try_join_async! { 255 | let branch_0 = ok(2u16) ~|> { 256 | let branch_0 = branch_0.as_ref().ok().cloned(); 257 | let branch_1 = branch_1.as_ref().ok().cloned(); 258 | let branch_2 = branch_2.as_ref().ok().cloned(); 259 | let branch_3 = branch_3.as_ref().ok().cloned(); 260 | move |value: Result| { 261 | assert_eq!(branch_0, value.as_ref().ok().cloned()); 262 | assert_eq!(branch_1, Some(3)); 263 | assert_eq!(branch_2, Some(4)); 264 | assert_eq!(branch_3, Some(5)); 265 | value.map(add_one_sync) 266 | } 267 | } ~=> add_one_ok, //4 268 | let branch_1 = three().await -> ok ~=> add_one_ok ~|> |value| value |> { 269 | let branch_0 = branch_0.as_ref().ok().cloned(); 270 | let branch_1 = branch_1.as_ref().ok().cloned(); 271 | let branch_2 = branch_2.as_ref().ok().cloned(); 272 | let branch_3 = branch_3.as_ref().ok().cloned(); 273 | move |value: Result| { 274 | assert_eq!(branch_0, Some(3)); 275 | assert_eq!(branch_1, value.as_ref().ok().cloned()); 276 | assert_eq!(branch_2, Some(5)); 277 | assert_eq!(branch_3, Some(5)); 278 | value.map(add_one_sync) 279 | } 280 | } ~|> add_one_to_ok ~|> add_one_to_ok, //7 281 | let branch_2 = ok_four() ~|> add_one_to_ok, //5 282 | let branch_3 = ok_five() ~|> |v| v ~|> add_one_to_ok ~=> to_err <= |_| ok(5) ~=> add_one_ok, // 6 283 | map => |a, b, c, d| a * b * c * d 284 | }; 285 | 286 | assert_eq!(product.await.unwrap(), 840); 287 | }); 288 | } 289 | 290 | #[test] 291 | fn it_checks_concurrent_branches_execution() { 292 | block_on(async { 293 | use futures::lock::Mutex; 294 | use std::{sync::Arc, time::Duration}; 295 | 296 | let values = Arc::new(Mutex::new(Vec::new())); 297 | 298 | join_async! { 299 | ok((values.clone(), 1u16)) => |(values, value)| async move { 300 | values.lock().await.push(value); 301 | Delay::new(Duration::from_secs(1)).await.unwrap(); 302 | { 303 | let mut values = values.lock().await; 304 | values.sort_unstable(); 305 | assert_eq!(values[..], [1, 2, 3]); 306 | values.pop(); 307 | } 308 | Ok::<_, BoxedError>((values, value + 1)) 309 | } ~=> |(values, value)| async move { 310 | values.lock().await.push(value); 311 | Delay::new(Duration::from_secs(1)).await.unwrap(); 312 | let mut values = values.lock().await; 313 | values.sort_unstable(); 314 | assert_eq!(values[..], [2, 3, 4]); 315 | Ok::<_, BoxedError>(()) 316 | }, 317 | ok((values.clone(), 2u16)) => |(values, value)| async move { 318 | values.lock().await.push(value); 319 | Delay::new(Duration::from_secs(2)).await.unwrap(); 320 | { 321 | let mut values = values.lock().await; 322 | values.sort_unstable(); 323 | assert_eq!(values[..], [1, 2]); 324 | values.pop(); 325 | } 326 | Ok::<_, BoxedError>((values, value + 1)) 327 | } ~=> |(values, value)| async move { 328 | values.lock().await.push(value); 329 | Delay::new(Duration::from_secs(2)).await.unwrap(); 330 | let mut values = values.lock().await; 331 | values.sort_unstable(); 332 | assert_eq!(values[..], [2, 3, 4]); 333 | Ok::<_, BoxedError>(()) 334 | }, 335 | ok((values.clone(), 3u16)) => |(values, value)| async move { 336 | values.lock().await.push(value); 337 | Delay::new(Duration::from_secs(3)).await.unwrap(); 338 | { 339 | let mut values = values.lock().await; 340 | values.sort_unstable(); 341 | assert_eq!(values[..], [1]); 342 | values.pop(); 343 | } 344 | Ok::<_, BoxedError>((values, value + 1)) 345 | } ~=> |(values, value)| async move { 346 | values.lock().await.push(value); 347 | Delay::new(Duration::from_secs(3)).await.unwrap(); 348 | let mut values = values.lock().await; 349 | values.sort_unstable(); 350 | assert_eq!(values[..], [2, 3, 4]); 351 | Ok::<_, BoxedError>(()) 352 | }, 353 | then => |_, _, _| async { 354 | assert_eq!(values.clone().lock().await[..], [2, 3, 4]); 355 | Ok::<_, BoxedError>(()) 356 | } 357 | } 358 | .await 359 | .unwrap(); 360 | }); 361 | } 362 | 363 | #[test] 364 | fn it_works_with_custom_joiner() { 365 | block_on(async { 366 | let values = join_async! { 367 | ready(2), ready(3) 368 | } 369 | .await; 370 | assert_eq!(values, (2, 3)); 371 | }); 372 | } 373 | 374 | #[test] 375 | fn it_produces_tuple() { 376 | block_on(async { 377 | let values = try_join_async! { 378 | futures_crate_path(::futures) 379 | ok::<_,()>(2), ok::<_,()>(3) 380 | } 381 | .await; 382 | assert_eq!(values.unwrap(), (2, 3)); 383 | }); 384 | } 385 | 386 | #[test] 387 | fn it_produces_single_value() { 388 | block_on(async { 389 | let value = try_join_async! { ready(Ok::<_,()>(1)) }.await; 390 | assert_eq!(value.unwrap(), 1); 391 | }); 392 | } 393 | 394 | #[allow(unreachable_code)] 395 | #[allow(clippy::diverging_sub_expression)] 396 | #[test] 397 | fn it_creates_unpolled_future() { 398 | block_on(async { 399 | let _fut = join_async! { 400 | ready(panic!()), 401 | ready(unreachable!()), 402 | then => |a: Result, _b: Result| ready(a) 403 | }; 404 | }); 405 | } 406 | 407 | #[test] 408 | fn it_works_with_references_correcly() { 409 | let mut arr = [1u8, 2, 3, 4, 5]; 410 | let refer = &mut arr; 411 | block_on(Box::pin(async move { 412 | let _ = try_join_async! { 413 | ok::<_,()>(refer) ~=> >>> ..into_iter().for_each(|v| { *v += 1; }) -> ok, 414 | } 415 | .await; 416 | })); 417 | assert_eq!(arr, [2, 3, 4, 5, 6]); 418 | } 419 | 420 | #[test] 421 | fn it_tests_multi_step_single_branch() { 422 | block_on(async { 423 | let values = try_join_async! { vec![1u8,2,3,4,5,6,7,8,9].into_iter() -> ok ~=> >>> ?> |v| v % 3 != 0 =>[] Vec<_> -> ok::<_,()> ~|> |v| v ~=> ok }.await.unwrap(); 424 | assert_eq!(values, vec![1u8, 2, 4, 5, 7, 8]); 425 | }); 426 | } 427 | 428 | #[test] 429 | fn it_tests_iter_combinators() { 430 | block_on(async { 431 | let mut some_vec = Ok(vec![0u8]); 432 | 433 | let values: (Vec, Vec) = join_async! { 434 | vec![2u8, 3, 4, 5, 6, 7, 8, 9, 10, 11].into_iter() |> |v| { some_vec = Err(5); v + 1 } ?|> |v| if v % 2 == 0 { Some(v) } else { None } |n> ^@ { some_vec.clone() }, |mut acc, (index, v)| { acc.as_mut().unwrap().push(v + (index as u8)); acc } ..unwrap().into_iter() =>[] Vec<_> ..into_iter() ?&!> |&n| (n as f64).cos().abs() > ::std::f64::consts::PI / 3f64 -> ready 435 | }.await; 436 | 437 | assert_eq!(values, (vec![], vec![0u8, 4, 7, 10, 13, 16])); 438 | 439 | let values = vec![0, 1u8, 2, 3, 4, 5, 6]; 440 | let other_values = vec![4u8, 5, 6, 7, 8, 9, 10]; 441 | 442 | assert_eq!( 443 | join_async! { 444 | { let values = values.clone(); values.into_iter() } >^> { let other_values = other_values.clone(); other_values.into_iter() } ?&!> |(v, v1)| v % 2 == 0 && v1 % 2 == 0 -> ready, 445 | }.await, 446 | ( 447 | vec![(0u8, 4u8), (2, 6), (4, 8), (6, 10)], 448 | vec![(1u8, 5u8), (3, 7), (5, 9)] 449 | ) 450 | ); 451 | 452 | let values = vec![0, 1u8, 2, 3, 4, 5, 6]; 453 | let other_values = vec![4u8, 5, 6, 7, 8, 9, 10]; 454 | 455 | assert_eq!( 456 | join_async! { 457 | { let values = values.clone(); values.into_iter() } >^> { let other_values = other_values.clone(); other_values.into_iter() } <-> u8, u8, Vec<_>, Vec<_> -> ready 458 | }.await, 459 | { 460 | let values = vec![0, 1u8, 2, 3, 4, 5, 6]; 461 | let other_values = vec![4u8, 5, 6, 7, 8, 9, 10]; 462 | (values, other_values) 463 | } 464 | ); 465 | 466 | assert_eq!( 467 | join_async! { vec![1u8, 2, 3, 4, 5].into_iter() ?> |v| v % 2 != 0 =>[] Vec<_> -> ready }.await, 468 | vec![1u8, 3, 5] 469 | ); 470 | 471 | assert_eq!( 472 | try_join_async! { let v = vec![1u8, 2, 3, 4, 5] ..into_iter() ?|>@ |v: u8| if v % 2 == 0 { Some(v) } else { None } -> |v: Option| ready(v.ok_or(Err::("e"))) }.await, 473 | Ok(2u8) 474 | ); 475 | 476 | assert_eq!( 477 | join_async! { vec![vec![1u8, 2, 3], vec![2]].into_iter() ^^> =>[] Vec<_> -> ready } 478 | .await, 479 | vec![1u8, 2, 3, 2] 480 | ); 481 | 482 | assert!( 483 | try_join_async! { vec![Ok(5u8), Err(4u8)].into_iter() ?^@ 0u8, |acc, v| v.map(|v| acc + v) -> ready }.await.is_err() 484 | ); 485 | }); 486 | } 487 | 488 | #[test] 489 | fn it_tests_nested_macro_combinations() { 490 | use futures::{executor::block_on, future::*}; 491 | use join::*; 492 | 493 | block_on(async { 494 | let value = try_join_async! { 495 | try_join_async! { 496 | ok::<_,()>(2u32), 497 | ok::<_,()>(3u32), 498 | ok::<_,()>(4u32), 499 | try_join_async! { 500 | ok::<_,()>(6u32), 501 | join_async! { 502 | ok::<_,()>(8u32), 503 | ok::<_,()>(9) ~=> |v| ok(v + 1) 504 | } |> |v| v.1, 505 | map => |a, b| b - a // 4 506 | }, 507 | map => |a, b, c, d| a + b + c + d // 13 508 | }, 509 | try_join_async!{ 510 | try_join_async! { 511 | ok::<_,()>(21u32), 512 | ok::<_,()>(22u32), 513 | ok::<_,()>(23u32), 514 | map => |a, b, c| a * b * c // 10626 515 | }, 516 | ok(2u32), 517 | and_then => |a, b| ok(a * b) // 21252 518 | }, 519 | map => |a, b| a + b // 21265 520 | } 521 | .await 522 | .unwrap(); 523 | 524 | assert_eq!(value, 21265); 525 | }); 526 | } 527 | 528 | #[test] 529 | fn it_tests_readme_demo_async_behaviour_and_requires_internet_connection() { 530 | use failure::format_err; 531 | use futures::{ 532 | future::{ok, ready, try_join_all}, 533 | stream::{iter, Stream}, 534 | }; 535 | use join::try_join_async; 536 | use reqwest::Client; 537 | use tokio::runtime::Runtime; 538 | 539 | let rt = Runtime::new().unwrap(); 540 | rt.block_on(async { 541 | println!("Hello.\nThis's is the game where winner is player, which number is closest to the max count of links (starting with `https://`) found on one of random pages.\nYou play against random generator (0-500)."); 542 | 543 | enum GameResult { 544 | Won, 545 | Lost, 546 | Draw 547 | } 548 | 549 | let client = Client::new(); 550 | 551 | let game = try_join_async! { 552 | // Make requests to several sites 553 | // and calculate count of links starting from `https://` 554 | urls_to_calculate_link_count() 555 | |> { 556 | // If pass block statement instead of fn, it will be placed before current step, 557 | // so it will us allow to capture some variables from context 558 | let client = &client; 559 | move |url| 560 | // `try_join_async!` wraps its content into `Box::pin(async move { })` 561 | try_join_async! { 562 | client 563 | .get(url).send() 564 | => |value| value.text() 565 | => |body| ok((url, body.matches("https://").count())) 566 | } 567 | } 568 | // Collect values into `Vec<_>` 569 | =>[] Vec<_> 570 | |> Ok 571 | => try_join_all 572 | !> |err| format_err!("Error retrieving pages to calculate links: {:#?}", err) 573 | => >>> 574 | ..into_iter() 575 | .max_by_key(|(_, link_count)| *link_count) 576 | .ok_or(format_err!("Failed to find max link count")) 577 | -> ready 578 | // It waits for input in stdin before log max links count 579 | ~?? >>> 580 | ..as_ref() 581 | |> |(url, count)| { 582 | let split = url.to_owned().split('/').collect::>(); 583 | let domain_name = split.get(2).unwrap_or(url); 584 | println!("Max `https://` link count found on `{}`: {}", domain_name, count) 585 | } 586 | ..unwrap_or(()), 587 | // Concurrently it makes request to the site which generates random number 588 | url_to_random_number() 589 | -> ok 590 | => { 591 | // If pass block statement instead of fn, it will be placed before current step, 592 | // so it will allow us to capture some variables from context 593 | let client = &client; 594 | let map_parse_error = |error, value| format_err!("Failed to parse random number: {:#?}, value: {}", error, value); 595 | move |url| 596 | try_join_async! { 597 | client 598 | .get(url) 599 | .send() 600 | => |value| value.text() 601 | !> |err| format_err!("Error retrieving random number: {:#?}", err) 602 | => |value| ok(value[..value.len() - 1].to_owned()) // remove \n from `154\n` 603 | => |value| 604 | ready( 605 | value 606 | .parse::() 607 | .map_err(|err| map_parse_error(err, value)) 608 | ) 609 | } 610 | } 611 | // It waits for input in stdin before log random value 612 | ~?? >>> 613 | ..as_ref() 614 | |> |number| println!("Random: {}", number) 615 | ..unwrap_or(()), 616 | // Concurrently it reads value from stdin 617 | read_number_from_stdin() |> Ok, 618 | // Finally, when we will have all results, we can decide, who is winner 619 | map => |(_url, link_count), random_number, number_from_stdin| { 620 | let random_diff = (link_count as i32 - random_number as i32).abs(); 621 | let stdin_diff = (link_count as i32 - number_from_stdin as i32).abs(); 622 | match () { 623 | _ if random_diff > stdin_diff => GameResult::Won, 624 | _ if random_diff < stdin_diff => GameResult::Lost, 625 | _ => GameResult::Draw 626 | } 627 | } 628 | }; 629 | 630 | game.await.map( 631 | |result| 632 | println!( 633 | "You {}", 634 | match result { 635 | GameResult::Won => "won!", 636 | GameResult::Lost => "lose...", 637 | _ => "have the same result as random generator!" 638 | } 639 | ) 640 | ).unwrap_or_else(|error| eprintln!("Error: {:#?}", error)); 641 | }); 642 | 643 | fn urls_to_calculate_link_count() -> impl Stream { 644 | iter( 645 | vec![ 646 | "https://en.wikipedia.org/w/api.php?format=json&action=query&generator=random&grnnamespace=0&prop=revisions|images&rvprop=content&grnlimit=100", 647 | "https://github.com/explore", 648 | "https://twitter.com/search?f=tweets&vertical=news&q=%23news&src=unkn" 649 | ] 650 | ) 651 | } 652 | 653 | fn url_to_random_number() -> &'static str { 654 | "https://www.random.org/integers/?num=1&min=0&max=500&col=1&base=10&format=plain&rnd=new" 655 | } 656 | 657 | async fn read_number_from_stdin() -> u16 { 658 | use tokio::io::{Error, ErrorKind}; 659 | 660 | loop { 661 | println!("Please, enter number (`u16`)"); 662 | let next = ok(Some("100")); 663 | 664 | let result = try_join_async! { 665 | next 666 | => >>> 667 | ..ok_or(Error::new(ErrorKind::Other, "Failed to read value from stdin")) 668 | => >>> 669 | ..parse() 670 | !> |err| Error::new(ErrorKind::Other, format!("Value from stdin isn't a correct `u16`: {:?}", err)) 671 | <<< 672 | -> ready 673 | }.await; 674 | 675 | if let Ok(value) = result { 676 | break value; 677 | } 678 | } 679 | } 680 | } 681 | 682 | #[test] 683 | fn it_tests_initial_block_capture() { 684 | use std::sync::Arc; 685 | 686 | block_on(async { 687 | let out = Arc::new(5); 688 | let value = try_join_async! { 689 | let pat_1 = { let out = out.clone(); ok::<_,()>(*out) }, 690 | let pat_2 = { ok::<_,()>(*out) }, 691 | map => |a, _| a 692 | } 693 | .await 694 | .unwrap(); 695 | 696 | assert_eq!(value, 5); 697 | }); 698 | } 699 | } 700 | --------------------------------------------------------------------------------