-
-
Notifications
You must be signed in to change notification settings - Fork 95
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
Update 'practical macros' section to use '=>' instead of '...' syntax… #107
Open
strangedreamsNY
wants to merge
1
commit into
Veykril:main
Choose a base branch
from
strangedreamsNY:update-practical-decl-macros
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,20 +32,23 @@ So, with all that having been said, let's get started. | |
## Construction | ||
|
||
Usually, when working on a new `macro_rules!` macro, the first thing I do is decide what the invocation should look like. | ||
In this specific case, my first attempt looked like this: | ||
In this specific case, my second* attempt looked like this: | ||
|
||
```rust,ignore | ||
let fib = recurrence![a[n] = 0, 1, ..., a[n-2] + a[n-1]]; | ||
let fib = recurrence![a[n] = 0, 1 => a[n-2] + a[n-1]]; | ||
|
||
for e in fib.take(10) { println!("{}", e) } | ||
``` | ||
|
||
> __\*__ It may be preferable to use '...' instead of '=>' for syntax like this and older versions of this book | ||
> certainly did just that, but there's a small problem with doing so that we'll discuss later on. Stick with '=>' for now. | ||
|
||
From that, we can take a stab at how the `macro_rules!` macro should be defined, even if we aren't sure of the actual expansion. | ||
This is useful because if you can't figure out how to parse the input syntax, then *maybe* you need to change it. | ||
|
||
```rust,ignore | ||
macro_rules! recurrence { | ||
( a[n] = $($inits:expr),+ , ... , $recur:expr ) => { /* ... */ }; | ||
( a[n] = $($inits:expr),+ => $recur:expr ) => { /* ... */ }; | ||
} | ||
# fn main() {} | ||
``` | ||
|
@@ -58,7 +61,7 @@ That rule says the input to the invocation must match: | |
- the literal token sequence `a` `[` `n` `]` `=`, | ||
- a [repeating] (the `$( ... )`) sequence, using `,` as a separator, and one or more (`+`) repeats of: | ||
- a valid *expression* captured into the [metavariable] `inits` (`$inits:expr`) | ||
- the literal token sequence `,` `...` `,`, | ||
- the literal token sequence `,` `=>` `,`, | ||
- a valid *expression* captured into the [metavariable] `recur` (`$recur:expr`). | ||
|
||
[repeating]: ./macros-methodical.md#repetitions | ||
|
@@ -132,7 +135,7 @@ Also, `TODO_shuffle_down_and_append` is another placeholder; | |
I want something that places `next_val` on the end of the array, shuffling the rest down by one space, dropping the 0th element. | ||
|
||
```rust,ignore | ||
|
||
/* ...snip */ | ||
Recurrence { mem: [0, 1], pos: 0 } | ||
}; | ||
|
||
|
@@ -185,11 +188,11 @@ In this case, we've added `u64`, but that's not necessarily what the user wants, | |
|
||
```rust | ||
macro_rules! recurrence { | ||
( a[n]: $sty:ty = $($inits:expr),+ , ... , $recur:expr ) => { /* ... */ }; | ||
( a[n]: $sty:ty = $($inits:expr),+ => $recur:expr ) => { /* ... */ }; | ||
} | ||
|
||
/* | ||
let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-2] + a[n-1]]; | ||
let fib = recurrence![a[n]: u64 = 0, 1 => a[n-2] + a[n-1]]; | ||
|
||
for e in fib.take(10) { println!("{}", e) } | ||
*/ | ||
|
@@ -273,12 +276,12 @@ The working code thus far now looks like this: | |
|
||
```rust | ||
macro_rules! recurrence { | ||
( a[n]: $sty:ty = $($inits:expr),+ , ... , $recur:expr ) => { /* ... */ }; | ||
( a[n]: $sty:ty = $($inits:expr),+ => $recur:expr ) => { /* ... */ }; | ||
} | ||
|
||
fn main() { | ||
/* | ||
let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-2] + a[n-1]]; | ||
let fib = recurrence![a[n]: u64 = 0, 1 => a[n-2] + a[n-1]]; | ||
|
||
for e in fib.take(10) { println!("{}", e) } | ||
*/ | ||
|
@@ -376,7 +379,7 @@ This gives us: | |
|
||
```rust,compile_fail | ||
macro_rules! recurrence { | ||
( a[n]: $sty:ty = $($inits:expr),+ , ... , $recur:expr ) => { | ||
( a[n]: $sty:ty = $($inits:expr),+ => $recur:expr ) => { | ||
{ | ||
/* | ||
What follows here is *literally* the code from before, | ||
|
@@ -447,90 +450,52 @@ macro_rules! recurrence { | |
} | ||
|
||
fn main() { | ||
let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-2] + a[n-1]]; | ||
let fib = recurrence![a[n]: u64 = 0, 1 => a[n-2] + a[n-1]]; | ||
|
||
for e in fib.take(10) { println!("{}", e) } | ||
} | ||
``` | ||
|
||
Obviously, we aren't *using* the metavariables yet, but we can change that fairly easily. | ||
However, if we try to compile this, `rustc` aborts, telling us: | ||
|
||
```text | ||
error: local ambiguity: multiple parsing options: built-in NTs expr ('inits') or 1 other option. | ||
--> src/main.rs:75:45 | ||
| | ||
75 | let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-2] + a[n-1]]; | ||
| | ||
``` | ||
|
||
Here, we've run into a limitation of the `macro_rules` system. | ||
The problem is that second comma. | ||
When it sees it during expansion, `macro_rules` can't decide if it's supposed to parse *another* expression for `inits`, or `...`. | ||
Sadly, it isn't quite clever enough to realise that `...` isn't a valid expression, so it gives up. | ||
Theoretically, this *should* work as desired, but currently doesn't. | ||
|
||
> **Aside**: I *did* fib a little about how our rule would be interpreted by the macro system. | ||
> In general, it *should* work as described, but doesn't in this case. | ||
> The `macro_rules` machinery, as it stands, has its foibles, and its worthwhile remembering that on occasion, you'll need to contort a little to get it to work. | ||
> **Aside**: I said we'd go over it before, so let's briefly review why we're using '=>' instead of '...' in our macro. | ||
> | ||
> If we used '...' for our input pattern matcher ala | ||
> ```rust,ignore | ||
> macro_rules! recurrence { | ||
> ( a[n] = $($inits:expr),+, ..., $recur:expr ) => { /* ... */ }; | ||
> // ^^^~~ changed | ||
> } | ||
> ``` | ||
> It _should_ allow us to make calls to `recurrence` that look like | ||
> ```rust,ignore | ||
> let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-2] + a[n-1]]; | ||
> ``` | ||
> but we'd receive the following error if we tried to compile: | ||
> ```text | ||
> error: local ambiguity: multiple parsing options: built-in NTs expr ('inits') or 1 other option. | ||
> --> src/main.rs:75:45 | ||
> | | ||
> 75 | let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-2] + a[n-1]]; | ||
> | | ||
> ``` | ||
> In general, it should work to have pattern matching like this, but doesn't in this case. | ||
> The `macro_rules` machinery, as it stands, has its foibles, and its worthwhile remembering that on occasion, | ||
> you'll need to contort a little to get it to work. | ||
> | ||
> In this *particular* case, there are two issues. | ||
> First, the macro system doesn't know what does and does not constitute the various grammar elements (*e.g.* an expression); that's the parser's job. | ||
> As such, it doesn't know that `...` isn't an expression. | ||
> As such, it doesn't know that the `...` in the above definition isn't an expression. | ||
> Secondly, it has no way of trying to capture a compound grammar element (like an expression) without 100% committing to that capture. | ||
> | ||
> In other words, it can ask the parser to try and parse some input as an expression, but the parser will respond to any problems by aborting. | ||
> The only way the macro system can currently deal with this is to just try to forbid situations where this could be a problem. | ||
> The only way the macro system can currently deal with this is to just try to forbid situations where this could be a problem. | ||
> Give it a shot now and play around with it to get a better idea of what we mean. | ||
> | ||
> On the bright side, this is a state of affairs that exactly *no one* is enthusiastic about. | ||
> The `macro` keyword has already been reserved for a more rigorously-defined future [macro system](https://github.com/rust-lang/rust/issues/39412). | ||
> Until then, needs must. | ||
|
||
Thankfully, the fix is relatively simple: we remove the comma from the syntax. | ||
To keep things balanced, we'll remove *both* commas around `...`: | ||
|
||
```rust,compile_fail | ||
macro_rules! recurrence { | ||
( a[n]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => { | ||
// ^~~ changed | ||
/* ... */ | ||
# // Cheat :D | ||
# (vec![0u64, 1, 2, 3, 5, 8, 13, 21, 34]).into_iter() | ||
}; | ||
} | ||
|
||
fn main() { | ||
let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-2] + a[n-1]]; | ||
// ^~~ changed | ||
|
||
for e in fib.take(10) { println!("{}", e) } | ||
} | ||
``` | ||
|
||
Success! ... or so we thought. | ||
Turns out this is being rejected by the compiler nowadays, while it was fine back when this was written. | ||
The reason for this is that the compiler now recognizes the `...` as a token, and as we know we may only use `=>`, `,` or `;` after an expression fragment. | ||
So unfortunately we are now out of luck as our dreamed up syntax will not work out this way, so let us just choose one that looks the most befitting that we are allowed to use instead, I'd say replacing `,` with `;` works. | ||
|
||
```rust | ||
macro_rules! recurrence { | ||
( a[n]: $sty:ty = $($inits:expr),+ ; ... ; $recur:expr ) => { | ||
// ^~~~~~^ changed | ||
/* ... */ | ||
# // Cheat :D | ||
# (vec![0u64, 1, 2, 3, 5, 8, 13, 21, 34]).into_iter() | ||
}; | ||
} | ||
|
||
fn main() { | ||
let fib = recurrence![a[n]: u64 = 0, 1; ...; a[n-2] + a[n-1]]; | ||
// ^~~~~^ changed | ||
|
||
for e in fib.take(10) { println!("{}", e) } | ||
} | ||
``` | ||
|
||
Success! But for real this time. | ||
|
||
|
||
### Substitution | ||
|
@@ -540,7 +505,7 @@ So, let's go through and fix the `u64`s: | |
|
||
```rust | ||
macro_rules! recurrence { | ||
( a[n]: $sty:ty = $($inits:expr),+ ; ... ; $recur:expr ) => { | ||
( a[n]: $sty:ty = $($inits:expr),+ => $recur:expr ) => { | ||
{ | ||
use std::ops::Index; | ||
|
||
|
@@ -582,29 +547,26 @@ macro_rules! recurrence { | |
fn next(&mut self) -> Option<$sty> { | ||
// ^~~~ changed | ||
/* ... */ | ||
# if self.pos < 2 { | ||
# let next_val = self.mem[self.pos]; | ||
# self.pos += 1; | ||
# Some(next_val) | ||
# } else { | ||
# let next_val = { | ||
# let n = self.pos; | ||
# let a = IndexOffset { slice: &self.mem, offset: n }; | ||
# (a[n-2] + a[n-1]) | ||
# }; | ||
# | ||
# { | ||
# use std::mem::swap; | ||
# | ||
# let mut swap_tmp = next_val; | ||
# for i in (0..2).rev() { | ||
# swap(&mut swap_tmp, &mut self.mem[i]); | ||
# } | ||
# } | ||
# | ||
# self.pos += 1; | ||
# Some(next_val) | ||
# } | ||
if self.pos < 2 { | ||
let next_val = self.mem[self.pos]; | ||
self.pos += 1; | ||
Some(next_val) | ||
} else { | ||
let next_val = { | ||
let n = self.pos; | ||
let a = IndexOffset { slice: &self.mem, offset: n }; | ||
(a[n-2] + a[n-1]) | ||
}; | ||
{ | ||
use std::mem::swap; | ||
let mut swap_tmp = next_val; | ||
for i in (0..2).rev() { | ||
swap(&mut swap_tmp, &mut self.mem[i]); | ||
} | ||
} | ||
self.pos += 1; | ||
Some(next_val) | ||
} | ||
Comment on lines
-585
to
+569
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is intentionally hidden, its noise regarding the change described |
||
} | ||
} | ||
|
||
|
@@ -614,7 +576,7 @@ macro_rules! recurrence { | |
} | ||
|
||
fn main() { | ||
let fib = recurrence![a[n]: u64 = 0, 1; ...; a[n-2] + a[n-1]]; | ||
let fib = recurrence![a[n]: u64 = 0, 1 => a[n-2] + a[n-1]]; | ||
|
||
for e in fib.take(10) { println!("{}", e) } | ||
} | ||
|
@@ -858,7 +820,7 @@ macro_rules! recurrence { | |
/* ... */ | ||
# | ||
# fn main() { | ||
# let fib = recurrence![a[n]: u64 = 0, 1; ...; a[n-2] + a[n-1]]; | ||
# let fib = recurrence![a[n]: u64 = 0, 1 => a[n-2] + a[n-1]]; | ||
# | ||
# for e in fib.take(10) { println!("{}", e) } | ||
# } | ||
|
@@ -934,7 +896,7 @@ With that done, we can now substitute the last thing: the `recur` expression. | |
# }; | ||
# } | ||
# fn main() { | ||
# let fib = recurrence![a[n]: u64 = 1, 1; ...; a[n-2] + a[n-1]]; | ||
# let fib = recurrence![a[n]: u64 = 1, 1 => a[n-2] + a[n-1]]; | ||
# for e in fib.take(10) { println!("{}", e) } | ||
# } | ||
``` | ||
|
@@ -945,25 +907,25 @@ And, when we compile our finished `macro_rules!` macro... | |
error[E0425]: cannot find value `a` in this scope | ||
--> src/main.rs:68:50 | ||
| | ||
68 | let fib = recurrence![a[n]: u64 = 1, 1; ...; a[n-2] + a[n-1]]; | ||
68 | let fib = recurrence![a[n]: u64 = 1, 1 => a[n-2] + a[n-1]]; | ||
| ^ not found in this scope | ||
|
||
error[E0425]: cannot find value `n` in this scope | ||
--> src/main.rs:68:52 | ||
| | ||
68 | let fib = recurrence![a[n]: u64 = 1, 1; ...; a[n-2] + a[n-1]]; | ||
68 | let fib = recurrence![a[n]: u64 = 1, 1 => a[n-2] + a[n-1]]; | ||
| ^ not found in this scope | ||
|
||
error[E0425]: cannot find value `a` in this scope | ||
--> src/main.rs:68:59 | ||
| | ||
68 | let fib = recurrence![a[n]: u64 = 1, 1; ...; a[n-2] + a[n-1]]; | ||
68 | let fib = recurrence![a[n]: u64 = 1, 1 => a[n-2] + a[n-1]]; | ||
| ^ not found in this scope | ||
|
||
error[E0425]: cannot find value `n` in this scope | ||
--> src/main.rs:68:61 | ||
| | ||
68 | let fib = recurrence![a[n]: u64 = 1, 1; ...; a[n-2] + a[n-1]]; | ||
68 | let fib = recurrence![a[n]: u64 = 1, 1 => a[n-2] + a[n-1]]; | ||
| ^ not found in this scope | ||
``` | ||
|
||
|
@@ -1211,7 +1173,7 @@ macro_rules! recurrence { | |
} | ||
|
||
fn main() { | ||
let fib = recurrence![a[n]: u64 = 0, 1; ...; a[n-2] + a[n-1]]; | ||
let fib = recurrence![a[n]: u64 = 0, 1 => a[n-2] + a[n-1]]; | ||
|
||
for e in fib.take(10) { println!("{}", e) } | ||
} | ||
|
@@ -1297,7 +1259,7 @@ Now, let's try with a different sequence. | |
# } | ||
# | ||
# fn main() { | ||
for e in recurrence!(f[i]: f64 = 1.0; ...; f[i-1] * i as f64).take(10) { | ||
for e in recurrence!(f[i]: f64 = 1.0 => f[i-1] * i as f64).take(10) { | ||
println!("{}", e) | ||
} | ||
# } | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is rather confusing wording, makes the reader question about the first attempt