Skip to content

Expand macro section on repetition operators and syntax. #185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
bin/*
stage/*
*~
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hide gedit temp files. I can remove it if you want...I just added it because it might be a good idea.

[EDIT]
Let me know if you want this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove it

18 changes: 18 additions & 0 deletions examples/staging/macros/arguments/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
The arguments of a macro are prefixed by a dollar sign `$` and type annotated
with a *designator*:

{designators.play}

This is a list of all the designators:

* `block`
* `expr` is used for expressions
* `ident` is used for variable/function names
* `item`
* `matchers` (lhs of the => in macro rules)
* `pat`
* `path`
* `stmt`
* `tt` (*token tree*) is used for operators and tokens
* `ty` (*type*)

42 changes: 1 addition & 41 deletions examples/staging/macros/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,5 @@ expanded into source code that gets compiled with the rest of the program.

Macros are created using the `macro_rules!` macro.

{simple.rs}
{simple.play}

{simple.out}

The arguments of a macro are prefixed by a dollar sign `$` and type annotated
with a *designator*.

{designators.rs}

{designators.out}

Macros can be overloaded to accept different combinations of arguments.

{overload.rs}

{overload.out}

Macros can use `+` in the argument list, to indicate that an argument may
repeat at least once, or `*`, to indicate that the argument may repeat zero or
more times.

{repeat.rs}

{repeat.out}

Macros allow writing DRY code, by factoring out the common parts of functions
and/or test suites. Here is an example that implements and tests the `+=`, `*=`
and `-=` operators on `Vec<T>`.

{dry.rs}

{dry.out}

```
$ rustc --test dry.rs && ./dry
running 3 tests
test test::mul_assign ... ok
test test::add_assign ... ok
test test::sub_assign ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured
```
51 changes: 51 additions & 0 deletions examples/staging/macros/repetition/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
Macros can also match and return repeated patterns. This is similar but
different from a regex. `$($x:expr),+` is a matching example. It uses the
`$()` grouping operator to mark `x` as repeatable. `+` or `*` denotes
repetition amount; one or more and zero or more respectively. `,`
is the only separator token available.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use other separators (as long as they are not ambiguous with operators like +, *, -, etc)

For example: ($($x:expr) and +) matches (1 and 2 and 3)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to generalize to |, &, and ; but |& did bitmath and ; possibly failed to parenthesis as before. I asked on IRC but couldn't get it working.


```rust
// The separator only checks separation; this is
// not a regex.
$($x:expr),+ // matches `1, 2, 3` but not `1, 2,`
```

When returned, it uses a similar notation: `$($x:expr),+` => `$($x),+`
however, a returned list will not be re-expanded whereas, matching
will find the whole thing.

```rust
#![feature(macro_rules)]

macro_rules! echo_broken {
// Attempt to match and return with one compact expression
($($x:expr),+) => { $($x),+ };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not broken for the reason you state "A list is never re-expanded". It's just that the expansion is not valid.

This echo_broken!(1u, 2u, 3u) expands to 1, 2, 3 which is not a valid expansion, because is not an expression.

But if you add parentheses to your macro definition, it'll work:

($($x:expr),+) => { ($($x),+) };

Now echo_broken!(1, 2, 3) expands to (1, 2, 3), which is a valid expression (is a tuple).

I can't think of an useful macro, using this non-recursive form of expansion. You could do something like this:

($($x:expr),+) => { $($x)-+ };

Now echo_broken!(3, 2, 1) expands to 3 - 2 - 1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting because I thought left and right had to match, that is:

($($x:expr),+) => { $($x),+ };
// Valid   ^             ^
($($x:expr),+) => { $($x)-+ };
// Invalid ^             ^
// because left and right don't match

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily.

I think the best way to understand these patterns is to read them:

(($x:expr),+) reads as expr separated by commas: (1, 2 , 3) (spaces are ignored)
(($x:ident) and +) reads as ident separated by ands: (foo and bar and baz)

Note that the separator must be between the "elements"

(($x:expr),+) won't match (1, 2, 3,) (note the trailing comma)
To deal with trailing commas, one must use this pattern: (($x:expr),+,) (note the comma after the +)

}

macro_rules! echo_works {
($x:expr) => { $x }; // one element list
// $x is the first element. `$($y:expr),+` is the tail
($x:expr, $($y:expr),+) => {
// pass the tail to `echo_works` so it can be expanded
($x, echo_works!($($y),+))
};
}

fn main() {
println!("{}", echo_broken!(1u)); // Works for single elements

// Errors: `,` ignored. A list is never re-expanded so when
// the `,` is reached, the rest of the line is ignored.
// Rust does not allow incomplete expansion, so it errors. To
// handle this, recursion is needed.
//println!("{}", echo_broken!(1u, 2u));

println!("{}", echo_works!(1u));
// Would like it flat but it nests the results...
println!("{}", echo_works!(1u, 2u, 3u, 4u, 5u, 6u));
}
```

Another example making a min macro:
{repeat.play}

4 changes: 4 additions & 0 deletions examples/staging/macros/separators/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Macros can be overloaded to accept different combinations of arguments.

{overload.play}

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![feature(macro_rules)]

macro_rules! assert_equal_len {
// The `tt` (token tree) designator is used for operators and tokens
($a:ident, $b: ident, $func:ident, $op:tt) => {
assert!($a.len() == $b.len(),
"{}: dimension mismatch: {} {} {}",
Expand Down Expand Up @@ -59,3 +60,4 @@ mod test {
test!(mul_assign, 2u, 3u, 6u)
test!(sub_assign, 3u, 2u, 1u)
}

16 changes: 16 additions & 0 deletions examples/staging/macros/test_suite/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Macros allow writing DRY code, by factoring out the common parts of functions
and/or test suites. Here is an example that implements and tests the `+=`, `*=`
and `-=` operators on `Vec<T>`.

{dry.play}

```
$ rustc --test dry.rs && ./dry
running 3 tests
test test::mul_assign ... ok
test test::add_assign ... ok
test test::sub_assign ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured
```

13 changes: 13 additions & 0 deletions examples/staging/macros/variable_args/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Variadic (variable number of arguments) functions can be implemented via
macros. Here we implement a macro similar to Python's overloaded
[range](https://docs.python.org/3/tutorial/controlflow.html#the-range-function) function:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving #185 (comment) so it's not hidden. Easier to remember.

implemented -> emulated. They are still macros, they are not variadic functions.
variadic -> This particular example range! resembles to function overloading, not to a variadic function.

My min! macro is more like a variadic function (it can have any arity, well, except 0-arity in this particular case).


```rust
// Here is how it should work
range!(10i) // Returns 0..9
range!(3i, 10) // Returns 3..9
range!(3i, 10, 2) // Returns 3, 5, 7, 9 (3..9 in increments of 2)
```

{range.play}

28 changes: 28 additions & 0 deletions examples/staging/macros/variable_args/range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#![feature(macro_rules)]

use std::iter::range_step;

// range! will take up to 3 inputs and call range
// function depending on inputs
macro_rules! range (
($end:expr) => {
range(0, $end)
};
($start:expr, $end:expr) => {
range($start, $end)
};
($start:expr, $end:expr, $step:expr) => {
range_step($start, $end, $step)
};
)

fn main() {
for i in range!(10i) { print!("{}", i) }
println!("");

for i in range!(3i, 10) { print!("{}", i) }
println!("");

for i in range!(3i, 10, 2) { print!("{}", i) }
}

8 changes: 7 additions & 1 deletion examples/structure.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,13 @@
{ "id": "staging", "title": "Staging Area", "children": [
{ "id": "bench", "title": "Benchmarking", "children": null },
{ "id": "ffi", "title": "Foreign Function Interface", "children": null },
{ "id": "macros", "title": "macro_rules!", "children": null },
{ "id": "macros", "title": "macro_rules!", "children": [
{ "id": "arguments", "title": "Arguments and designators", "children": null },
{ "id": "variable_args", "title": "Variadic macros", "children": null },
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove Variadic macros. See #185 (comment)

{ "id": "separators", "title": "Variable separator options", "children": null },
{ "id": "repetition", "title": "Repetition and expansion", "children": null },
{ "id": "test_suite", "title": "Generating generic test suite implementations", "children": null }
] },
{ "id": "rand", "title": "Random", "children": null },
{ "id": "simd", "title": "SIMD", "children": null },
{ "id": "test", "title": "Testing", "children": null },
Expand Down