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

Interpolate blocks #276

Closed

Conversation

Pistonight
Copy link

This is my design/attempt at implementing #275

Proposed Syntax

I spent a lot of time thinking about what the syntax should be. IMO, it should:

  1. Compatible with existing syntax and design language (i.e. use # to indicate interpolation)
  2. Allow users to easily tell if a block is quoted vs interpolated
  3. Allow users to easily tell where the start and end of that block is
  4. Be intuitive and does not invent a very different syntax
  5. Does not introduce ambiguity or tricky edge cases

I started with #{ ... }, went though many iterations and in the end settled on #@{ ... }@:

  • It uses # to indicate start interpolating, which is consistent with other interpolation pattern
  • @ and {} are commonly used for interpolation
  • It doesn't have edge cases like chaining the end of a block into the start of another

Proposed Behavior

There are 2 ways where this could be implemented, with different behaviors:

  1. A separated pass to extract all interpolation block, and execute them before the second pass to process idents and repetitions
  2. Still keep the single pass design, and execute the block where it's at and add the output to the running TokenStream

I chose 2 since the implementation is way simpler, and probably more intuitive when composed with #( ... )*. This implementation also allows the blocks to access the bindings generated by #( ... )*, which becomes useful if we make RepInterp implement Deref. Just a dummy example:

let a = quote! { a };
let b = quote! { b };
let ids2 = vec![ vec![&a, &b], vec![&b, &a] ];

let tokens = quote! {
    #(
        invoke(#(#ids2),*);
        #@{
            let mut t = TokenStream::new();
            t.extend(ids2[0].clone());
            t.extend(quote! { + });
            t.extend(ids2[1].clone());
            t
        }@
    )*
};
// output: invoke (a, b) ; a + b invoke (b, a) ; b + a

Also, for ergonomic reasons, it's ideal for the macro to handle both when the block outputs a ToTokens or an iterator of them. This is implemented with a same trick used for repetition where a to_tokens method shadows the trait method if the trait is not implemented

Other

  • Any issue/edge case with the syntax?
  • Any issue with implementing Deref for RepInterp?

Please let me know if there are other concerns

Tasks

  • Design
  • Tests
  • Implementation
  • Update documentation
  • Compile fail tests

Copy link
Owner

@dtolnay dtolnay left a comment

Choose a reason for hiding this comment

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

Thanks for the PR!

Something like this is proposed every now and then (e.g. #88) and I would prefer not to support it.

I think there are some other more fully-featured quasiquoting libraries that come with this feature, which you could use in your macros instead of quote.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants