Skip to content
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

Add documentation for meta variable expressions #1485

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
270 changes: 270 additions & 0 deletions src/macros-by-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,276 @@ compiler knows how to expand them properly:
not have the same number. This requirement applies to every layer of nested
repetitions.

## Metavariable expressions

Metavariable expressions live within a special braced syntax `${...}` and are function-like operators used to access "meta" information about how macros get matched and expanded, such as how many items get matched in a repetition group. This information is typically difficult or even
impossible to otherwise obtain.

### count($ident, depth=0)

Expands to an unsuffixed integer literal representing the number of times a ***repetition*** repeats in total. That is, this will be the number of times that a repetition would be expanded.

The output of `count` depends on where it is placed as well the provided index. If no index is provided, then it will always start at the innermost level.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The output of `count` depends on where it is placed as well the provided index. If no index is provided, then it will always start at the innermost level.

I would give a simple example first and mention depth after

Copy link
Member

Choose a reason for hiding this comment

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

I think this shouldn't be removed, rather it should be expanded to fully describe the exact algorithm used to determine the result. I assume it's something like this but I don't know the right terms for all the concepts involved (and I only experimented with depth=0).

Copy link
Member

@RalfJung RalfJung May 30, 2024

Choose a reason for hiding this comment

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

In general, it seems to be something like

  • let's say $ident occurs under N repetition groups in the matcher
  • and now we use ${count($ident, D)} under M repetition groups in the transcriber
  • then we must have M+D < N
  • and we are counting how often the repetition group D up from $ident in the matcher occurs inside the current iteration of the M outer repetitions in the transcriber. IOW, for the case where D=0 (IMO the only one we should stabilize for now), we are counting how many times $ident would expand if we surrounded $ident in enough repetition groups to make it legal to occur at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In general, it seems to be something like

* let's say `$ident` occurs under N repetition groups in the matcher

* and now we use `${count($ident, D)}` under M repetition groups in the transcriber

* then we must have M+D < N

* and we are counting how often the repetition group D up from `$ident` in the matcher occurs inside the current iteration of the M outer repetitions in the transcriber. IOW, for the case where D=0 (IMO the only one we should stabilize for now), we are counting how many times `$ident` would expand  if we surrounded `$ident` in enough repetition groups to make it legal to occur at this point.

That is a good explanation. Specially in the "counting how often the repetition group D up from $ident in the matcher occurs" highlighting that $ident is just a matcher reference when using indexes.


```rust
macro_rules! count_idents {
( $( $i:ident ),* ) => {
${count($i)}
};
}

fn main() {
assert_eq!(count_idents!(a, b, c), 3);
}
```

If repetitions are nested, then the optional depth parameter can be used to
count the repetitions of a parent repetition group.

Let `M` be the number of repetition groups in the matcher, `T` the depth placement in the transcriber and `C` the written depth in `count`, then it is possible to infer that `M > T + C`.

We are counting how often the repetition group `C` up from a metavariable in the matcher occurs inside the current iteration of the `T` outer repetitions in the transcriber, effectively making the metavariable a reference or starting point.

```rust
macro_rules! count_value {
( $( $name:ident: $( $value:literal ),* );+ ) => {
// Count the total number of times that the (innermost) group
// containing `$value` gets matched.
${count($value)}
// This is the same as `${count($value, 0)}`
};
}
macro_rules! count_name1 {
( $( $name:ident: $( $value:literal ),* );+ ) => {
// This is one way to get the number of times that the group
// containing `$name` gets matched. Alternatively...
${count($name)}
};
}
macro_rules! count_name2 {
( $( $name:ident: $( $value:literal ),* );+ ) => {
// ...`$value` can be used again with a depth specifier of 1,
// indicating that repetitions of the 1st parent group of
// `$value` should be counted, rather than `$value`'s innermost group.
//
// `1` is the maximum value here since `$value`'s group has a single
// parent.
${count($value, 1)}
};
}
macro_rules! count_value_nested {
( $( $name:ident: $( $value:literal ),* );+ ) => {
[ $(
// using `count` within a repetition group will return the number
// of times `$value` is matched _within that group_.
${count($value)},
)+ ]
};
}
fn main() {
// all instances of `$value` counted: count(1, 2, 3, 4, 5) = 5
assert_eq!(count_value!(a: 1, 2, 3; b: 4, 5), 5);
// count(1, 2, 3, ... 11) = 11
assert_eq!(
count_value!(a: 1, 2, 3; b: 4, 5; c: 6, 7; d: 8, 9, 10, 11),
11
);
// `$value` is never matched; count() = 0
assert_eq!(count_value!(a:), 0);
// count(a, b) = 2 matches
assert_eq!(count_name1!(a: 1, 2, 3; b: 4, 5), 2);
// count(a, b, c, d) = 4 matches
assert_eq!(
count_name1!(a: 1, 2, 3; b: 4, 5; c: 6, 7; d: 8, 9, 10, 11),
4
);
// count(a) = 1 match
assert_eq!(count_name1!(a:), 1);
// These have the same results as the above
assert_eq!(count_name2!(a: 1, 2, 3; b: 4, 5), 2);
assert_eq!(
count_name2!(a: 1, 2, 3; b: 4, 5; c: 6, 7; d: 8, 9, 10, 11),
4
);
assert_eq!(count_name2!(a:), 1);
// first match: count(1, 2, 3) = 3. second match: count(4, 5) = 2.
assert_eq!(count_value_nested!(a: 1, 2, 3; b: 4, 5), [3, 2]);
// first match: count(1, 2, 3) = 3. second match: count(4, 5) = 2.
// third match: count(6, 7) = 4. fourth match: count(8, 9, 10, 11) = 4.
assert_eq!(
count_value_nested!(a: 1, 2, 3; b: 4, 5; c: 6, 7; d: 8, 9, 10, 11),
[3, 2, 2, 4]
);
// `$value` is never matched; count() = 0
assert_eq!(count_value_nested!(a:), [0]);
}
```

`count` can not be placed inside the repetition depth of its referenced metavariable, otherwise the output would always be 1.

### ignore($ident)

Sometimes it is desired to repeat an expansion the same number of times as a metavariable repeats but without actually expanding the metavariable.

```rust
macro_rules! count {
( $( $i:ident ),* ) => {{
0 $( + 1 ${ignore($i)} )*
}};
}
fn main() {
assert_eq!(count!(a, b, c), 3);
}
```

The `ignore` metavariable acts as if the ident was used for the purposes of repetition, but expands to nothing.

### index(depth=0)

Expands to an unsuffixed integer literal representing the current iteration index of a ***repetition*** at a given depth.

The output of `index` depends on where it is placed as well the provided index. If no index is provided, then it will always start at the innermost level.

```rust
/// The length of all items within a tuple
trait TotalLen {
fn total_len(&self) -> usize;
}
/// Implement `TotalLen` for a n-length tuple
macro_rules! impl_tuple {
( $( $generic:ident ),* ) => {
impl<$( $generic, )*> TotalLen for ($( $generic, )*)
where
$( $generic: AsRef<[u8]>, )*
{
fn total_len(&self) -> usize {
let mut sum: usize = 0;
$({ // repetition
${ignore($generic)}
// `${index()}` will expand to 0, 1, 2... for each tuple element
sum = sum.wrapping_add(self.${index()}.as_ref().len());
})* // end repetition
sum
}
}
};
}
// Implement for length 2 tuple
impl_tuple!(T1, T2);
// Implement for length 3 tuple
impl_tuple!(T1, T2, T3);

fn main() {
assert_eq!(([1, 2, 3], [4, 5]).total_len(), 5);
assert_eq!(([1].as_slice(), vec![2, 3]).total_len(), 3);
}
```

If repetitions are nested, then the optional depth parameter can be used to
count the repetitions of a parent repetition group.

```rust
macro_rules! innermost0 {
( $( $a:ident: $( $b:literal ),* );+ ) => {
// Count the index of `$b`'s group
[$( $( ${ignore($b)} ${index()}, )* )+]
c410-f3r marked this conversation as resolved.
Show resolved Hide resolved
};
}

macro_rules! innermost1 {
( $( $a:ident: $( $b:literal ),* );+ ) => {
// Count the index of `$b`'s parent repetition group, i.e. `$a`'s group
[$( $( ${ignore($b)} ${index(1)}, )* )+]
c410-f3r marked this conversation as resolved.
Show resolved Hide resolved
};
}

macro_rules! outermost {
( $( $a:ident: $( $b:literal ),* );+ ) => {
// // Count the index of `$a`'s group directly
[$( ${ignore($a)} ${index()}, )+]
c410-f3r marked this conversation as resolved.
Show resolved Hide resolved
};
}

fn main() {
// 1 2 3 = 3 elements
// 4 5 = 2 elements
//
// Increasing list from the innermost loop referring innermost indexes
assert_eq!(innermost0!(a: 1, 2, 3; b: 4, 5), [0, 1, 2, 0, 1]);

// a b = 2 elements
//
// Increasing list from the innermost loop referring outermost indexes
assert_eq!(innermost1!(a: 1, 2, 3; b: 4, 5), [0, 0, 0, 1, 1]);

// a b = 2 elements
//
// Increasing list from the outermost loop referring outermost indexes
assert_eq!(outermost!(a: 1, 2, 3; b: 4, 5), [0, 1]);
}
```

### len(depth=0)

Expands to an unsuffixed integer literal representing the sum or length of a ***repetition*** at a given depth.

The output of `len` depends on where it is placed as well the provided index. If no index is provided, then it will always start at the innermost level.

```rust
macro_rules! array {
( $( $i:ident ),* ) => {
[$( ${len()}, )*]
};
}

fn main() {
assert_eq!(array!(A, B, C), [3, 3, 3]);
}
```

If repetitions are nested, then the optional depth parameter can be used to
count the repetitions of a parent repetition group.

```rust
macro_rules! innermost0 {
( $( $a:ident: $( $b:literal ),* );+ ) => {
[$( $( ${ignore($b)} ${len()}, )* )+]
};
}

macro_rules! innermost1 {
( $( $a:ident: $( $b:literal ),* );+ ) => {
[$( $( ${ignore($b)} ${len(1)}, )* )+]
};
}

macro_rules! outermost {
( $( $a:ident: $( $b:literal ),* );+ ) => {
[$( ${ignore($a)} ${len()}, )+]
};
}

fn main() {
// 1 2 3 = 3 elements
// 4 5 = 2 elements
//
// 3 and 2 elements repeating 3 and 2 times in the innermost loop
assert_eq!(innermost0!(a: 1, 2, 3; b: 4, 5), [3, 3, 3, 2, 2]);

// a b = 2 elements
//
// 2 elements repeating 5 times in the innermost loop
assert_eq!(innermost1!(a: 1, 2, 3; b: 4, 5), [2, 2, 2, 2, 2]);

// a b = 2 elements
//
// 2 elements repeating 2 times in the outermost loop
assert_eq!(outermost!(a: 1, 2, 3; b: 4, 5), [2, 2]);
}
```

Unlike `count`, `len` must be placed inside a repetition.

## Scoping, Exporting, and Importing

For historical reasons, the scoping of macros by example does not work entirely
Expand Down
Loading