Chaining Computations
The Elixir language allows for chaining computations using its |>
operator, which inserts the left-hand argument as the first argument of the function call on the right-hand argument:
iex> "Elixir" |> String.graphemes() |> Enum.frequencies()
%{"E" => 1, "i" => 2, "l" => 1, "r" => 1, "x" => 1}
We would like to achieve the same thing in Rust using a visitor. Since we want to use a visitor but using syn
builtin parsing capabilities, we will take a valid Rust tree as input. Therefore, we will sacrifice the |
character (pipe, which represents "bitwise or") for this purpose.
#[pipe]
fn pipe_example() {
let f = "Rust" | str::chars | Vec::from_iter;
assert_eq!(f, vec!['R', 'u', 's', 't']);
}
This code is equivalent to:
fn pipe_example() {
let f = Vec::from_iter(str::chars("Rust"));
assert_eq!(f, vec!['R', 'u', 's', 't']);
}
Our #[pipe]
macro and its |
operator should also support function calls with multiple arguments. The expression a | f(b, c)
is equivalent to f(a, b, c)
.
Implementation
Exercise 6.a: Use a syn::visit_mut::VisitMut
visitor to intercept the analysis of expressions and replace a binary expression using the |
operator followed by a function call or a path with a function call that uses the left-hand argument as the first argument of the call. Remember to recurse to visit the new node (or the sub-nodes if no transformation was made).
Consider what type of node you want to visit in order to modify it. For example, if you are modifying a ExprBinary
node that represents a binary expression, you cannot replace it with a node that is not a binary expression. If you are modifying an Expr
node that is less specific, it allows you to replace an expression with another.
Also, remember that you can use syn::parse_quote!()
to generate nodes of any type from a template.
Exercise 6.b: Create a procedural macro attribute pipe
that uses this visitor to transform any item using the visitor.
Exercise 6.c: Add tests to verify the correct behavior of this macro, which should be applicable to functions, modules, and implementations.