Макрос для построения перечисления с различными «видами» элементов - PullRequest
0 голосов
/ 24 сентября 2019

Я пытаюсь придумать макрос, который я бы назвал как

create_states!(S0, S1, final S2, final S3);

. Он создаст перечисление для представления состояний конечных автоматов, и некоторые из них будут конечными (принимающими) состояниями - S2, S3.Полученное перечисление и его impl должны выглядеть следующим образом:

enum State {
    S0,
    S1, 
    S2,
    S3,
}

impl State {
    fn is_final(&self) -> bool {
        match self {
            Self::S2 => true,
            Self::S3 => true,
            _ => false,
        }
    }
}

Моя наивная попытка:

macro_rules! create_states {
    ($($r:ident),+, $(final $f:ident),*) => {
        #[derive(Copy, Clone)]
        enum State {
            $($s),*
            $($f),*
        }

        impl State {
            fn is_final(&self) -> bool {
                match self {
                    $(Self::$f => true,)*
                    _ => false,
                }
            }
        }
    }
}

заканчивается следующей ошибкой:

error: local ambiguity: multiple parsing options: built-in NTs ident ('r') or 1 other option.
  --> src/lib.rs:20:24
   |
20 | create_states!(S0, S1, final S2, final S3);
   |                        ^^^^^

Попытка удалить запятую между шаблонами во второй строке:

($($r:ident),+ $(final $f:ident),*) => { ...

создает другую:

error: no rules expected the token `S2`
  --> src/lib.rs:20:30
   |
1  | macro_rules! create_states {
   | -------------------------- when calling this macro
...
20 | create_states!(S0, S1, final S2, final S3);
   |                              ^^ no rules expected this token in macro call

Я думаю Я понимаю, что вызывает этиошибки - он думает, что final - это другой идентификатор, соответствующий r.Но как правильно написать такой макрос (если это вообще возможно, без чрезмерного усложнения)?

Я обладаю полной гибкостью при вызове макросов, так как это мое личное упражнение.Основная цель - научиться правильно делать вещи.Было бы неплохо, чтобы этот макрос принимал final в любой позиции, если это возможно.

Ответы [ 2 ]

3 голосов
/ 24 сентября 2019

Это может быть достигнуто с помощью TT измельчителя , накопления при нажатии и обработки концевых сепараторов .

macro_rules! create_states {
    // User entry points.
    (final $name:ident $($tt:tt)*) => {
        create_states!(@ {[] [$name]} $($tt)*);
    };
    ($name:ident $($tt:tt)*) => {
        create_states!(@ {[$name] []} $($tt)*);
    };

    // Internal rules to categorize each value
    (@ {[$($n:ident)*] [$($t:ident)*]} $(,)? final $name:ident $($tt:tt)*) => {
        create_states!(@ {[$($n)*] [$($t)* $name]} $($tt)*);
    };
    (@ {[$($n:ident)*] [$($t:ident)*]} $(,)? $name:ident $($tt:tt)*) => {
        create_states!(@ {[$($n)* $name] [$($t)*]} $($tt)*);
    };

    // Final internal rule that generates the enum from the categorized input
    (@ {[$($n:ident)*] [$($t:ident)*]} $(,)?) => {
        #[derive(Copy, Clone)]
        enum State {
            $($n,)*
            $($t,)*
        }

        impl State {
            fn is_final(&self) -> bool {
                match self {
                    $(Self::$t => true,)*
                    _ => false,
                }
            }
        }
    };
}

См. Также:

1 голос
/ 25 сентября 2019

Ответ Шепмастера является более общим, но в вашем конкретном случае, поскольку вы «обладаете полной гибкостью при вызове макроса», вы можете просто заменить final на @final, и наивная попытка сработает, за исключением нескольких незначительных опечаток:

macro_rules! create_states {
    ($($r:ident),+, $(@final $f:ident),*) => {
        #[derive(Copy, Clone)]
        enum State {
            $($r,)*
            $($f),*
        }

        impl State {
            fn is_final(&self) -> bool {
                match self {
                    $(Self::$f => true,)*
                    _ => false,
                }
            }
        }
    }
}

create_states!(S0, S1, @final S2, @final S3);

детская площадка

...