First macro help: matching a literal string as a flag?

Hello,

This year, I'm (slowly) making my way through Advent of Code 2018 as a way to learn rust. I'm trying to learn a new rust feature with each new problem.

I've just finished Day 16, in which I created a number of functions following a very similar signature and pattern, and I wondered if it might be a good opportunity to learn to write my first macro that would define these functions. I've read The Little Book of Rust Macros, the Rust Book, and tried to do my homework with several SO and forum searches, but I am not a programmer by training, so some of these concepts are tough for me.

I'm hoping to create a macro that I can call with something like op!(addi, reg C = reg A + B) and defines a function named addi (name will vary with each call to the macro) that accepts two &[usize; 4] and returns a &[usize; 4] (same for each call to the macro). The letters A, B, and C, the operator (+ in this case), and the preceding presence or absence of reg before this letter all determine how the function runs, and these will vary.

The first trouble spot I'm running into is figuring out how to match things like reg, or A, which will inform my macro regarding how to write the function body. reg will function like a flag for each letter -- what kind of metavariable does that make it? A literal? How can I match for the presence or absence of a literal reg? Something like $($first_reg:literal)? Similarly, for the A, B, or C -- only those three letters should be accepted (and in other cases the macro should fail to compile). Does that make the metavariable a pattern? I wouldn't think so, since the macro doesn't accept a pattern, just a letter that I want to ensure matches a pattern.

My thoughts so far in trying to figure out the pattern:

macro_rules! op {
    ($name:ident, $($out_reg:"reg")? $out:(A|B|C) = $($lhs_reg:"reg")? $lhs:(A|B|C) $operator: $($rhs_reg:"reg")? $rhs:(A|B|C)) => {
        fn $name(command: &[usize; 4], input: &[usize; 4]) -> [usize; 4] {
            // WIP
        }
    };
}

Obviously I haven't made much progress, but I appreciate any suggestions or resources. TIA!

EDIT: Change title -- not necessarily a boolean flag
EDIT2: Grammar

Matching the presence or absence of a token like that is not easy; usually I split the macro up into rules to facilitate parsing, using the techniques described in the book you already found. But if you want to do it compactly, I think you can hack it:

$( reg $(@ $reg_found:pat @)* )?

The purpose of the @ is to serve as an unusual token that shouldn't appear. The :pat can be anything (:expr, whatever); it should never be matched, and only serves to name the repetition. You could use repetition replacement to turn it into a compile_error! on the off-chance that it is matched.


However, your syntax looks sufficiently complex enough that you should be breaking the parsing up into steps, as a tt muncher. (However I am on my phone and cannot compose a detailed example)

1 Like

Thanks for your thoughts and feedback. I'm surprised to hear that this sounds like a relatively complicated thing to do -- is this a problem for which there is a better "rust way" (basically a kind of if / else flag for macros)? It certainly seems like there are a few paradigms that take some getting used to.

I may look into building a tt muncher, perhaps that's the way to go. Thanks again.

Procedural macros are significantly more powerful and more maintainable, but they require a fair bit more ceremony to create.

This will let your macro perform arbitrary rust code to parse and process the text. Most likely, you will want to depend on the syn crate (to parse any standard rust syntax like types) and the quote crate (for interpolated output).

Nice sentence :slight_smile:

1 Like