aboutsummaryrefslogtreecommitdiffstats
path: root/src/runtime.rs
diff options
context:
space:
mode:
authorNika Layzell <nika@thelayzells.com>2019-06-29 23:34:33 -0400
committerNika Layzell <nika@thelayzells.com>2019-06-30 01:42:14 -0400
commita4eb731f73025eb9e4201b0a53d423860e289682 (patch)
treee8d8e8214957301435fb42c2e6699c55acdd6b81 /src/runtime.rs
parenta5227dab205915d45bc1f1b90ea7a3fe996346a4 (diff)
downloadplatform_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.rs36
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)
}