diff options
author | Nika Layzell <nika@thelayzells.com> | 2019-06-29 23:34:33 -0400 |
---|---|---|
committer | Nika Layzell <nika@thelayzells.com> | 2019-06-30 01:42:14 -0400 |
commit | a4eb731f73025eb9e4201b0a53d423860e289682 (patch) | |
tree | e8d8e8214957301435fb42c2e6699c55acdd6b81 /src/runtime.rs | |
parent | a5227dab205915d45bc1f1b90ea7a3fe996346a4 (diff) | |
download | platform_external_rust_crates_quote-a4eb731f73025eb9e4201b0a53d423860e289682.tar.gz platform_external_rust_crates_quote-a4eb731f73025eb9e4201b0a53d423860e289682.tar.bz2 platform_external_rust_crates_quote-a4eb731f73025eb9e4201b0a53d423860e289682.zip |
Support duplicate interpolations within repetitions (#8)
This eliminates the restrictions around duplicate interpolations by taking
advantage of shadowing of let bindings within generated loops and rust deref
coersion within a desugared loop.
Duplicate calls to `IntoIter::into_iter` are handled, as the duplicates will be
called on the already-bound iterator objects, meaning that the call is a no-op.
Duplicate calls to `next()` are also handled by wrapping each interpolation
within the loop into a `RepInterp<T>` wrapper. This wrapper provides a dummy
inherent `next` method which ensures that `next` is only called once per
iterator per loop.
As a side-benefit, this has the effect of producing `unused_code` warnings when
no interpolations occur within a repetition block, as rustc can see an
unconditional break statement before code to generate the repetition body.
This has been tested working in rustc 1.15.1, although extraneous `unused_mut`
warnings are emitted due to `#[allow(unused_mut)]` being ignored on statements
in that release.
The generated code looks similar to the following:
```rust
// quote!(#(#a #b #a),*);
// ...
{
let mut _i = 0; // Only used if sep is present.
// Get and bind iterators to use for the capture repetition.
#[allow(unused_mut)] let mut a = a.into_iter();
#[allow(unused_mut)] let mut b = b.into_iter();
// Duplicate names are a no-op, as IntoIter::into_iter is idempotent.
#[allow(unused_mut)] let mut a = a.into_iter();
loop {
// Calls `Iterator::next` and wraps the result in `RepInterp` if `Some`.
let a = match a.next() {
Some(_x) => $crate::__rt::RepInterp(_x),
None => break,
};
let b = match b.next() {
Some(_x) => $crate::__rt::RepInterp(_x),
None => break,
};
// No-op `next()` call for duplicate names, as `RepInterp` defines an
// inherent `next(self) -> Option<T>` method.
let a = match a.next() {
Some(_x) => $crate::__rt::RepInterp(_x),
None => break,
};
if _i > 0 {
quote_each_token!(tokens span ,);
}
_i += 1;
quote_each_token!(tokens span #a #b #a);
}
}
// ...
```
Diffstat (limited to 'src/runtime.rs')
-rw-r--r-- | src/runtime.rs | 36 |
1 files changed, 35 insertions, 1 deletions
diff --git a/src/runtime.rs b/src/runtime.rs index 75337c9..d001a1c 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,6 +1,40 @@ use ext::TokenStreamExt; +use ToTokens; pub use proc_macro2::*; +// Helper type used within interpolations to allow for repeated binding names. +// Implements the relevant traits, and exports a dummy `next()` method. +#[derive(Copy, Clone)] +pub struct RepInterp<T>(pub T); + +impl<T> RepInterp<T> { + // This method is intended to look like `Iterator::next`, and is called when + // a name is bound multiple times, as the previous binding will shadow the + // original `Iterator` object. This allows us to avoid advancing the + // iterator multiple times per iteration. + #[inline] + pub fn next(self) -> Option<T> { + Some(self.0) + } +} + +impl<T: IntoIterator> IntoIterator for RepInterp<T> { + type Item = T::Item; + type IntoIter = T::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<T: ToTokens> ToTokens for RepInterp<T> { + #[inline] + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0.to_tokens(tokens); + } +} + fn is_ident_start(c: u8) -> bool { (b'a' <= c && c <= b'z') || (b'A' <= c && c <= b'Z') || c == b'_' } @@ -12,7 +46,7 @@ fn is_ident_continue(c: u8) -> bool { fn is_ident(token: &str) -> bool { let mut iter = token.bytes(); let first_ok = iter.next().map(is_ident_start).unwrap_or(false); - + first_ok && iter.all(is_ident_continue) } |