Как разобрать одиночные токены в макросах ржавчины - PullRequest
3 голосов
/ 04 мая 2019

Я начинаю играть с макросами Rust и пришел, чтобы попробовать этот маленький практический пример. Я хочу определить макрос, который расширяется до инициализации переменной (имя не имеет значения) типа i32 (например, но не очень важно) и серии операций с этой переменной, в данном случае var += 1 или var -= 1 и, наконец, он будет вызывать println!("{}", var). Макрос будет принимать серию токенов на основе + и -, которые соответствуют операциям, описанным выше.

Так, например:

operate_integer![+++---]

расширится до:

let mut var: i32 = 0;
var += 1;
var += 1;
var += 1;
var -= 1;
var -= 1;
var -= 1;
print!("{}", var);

Я решил использовать для этого 2 макроса, один для упаковки инициализации и печати, а другой для оценки токенов +-:

Базовым будет:

macro_rules! operate_integer {
    // $($all_tokens:tt)* should match everything, it will be forward to the helper macro
    ($($all_tokens:tt)*) => {
        let mut var : i32 = 0;
        operate_integer_helper![$($all_tokens:tt)*]
        print!("{}", var);
    }
}

Помощник расширит операции:

macro_rules! operate_integer_helper {
    // the idea is that it matches a `+` followed by more tokens
    (+$($t:tt)*) => {
        val += 1;
        operate_integer_helper![$($t:tt)*] // we recursively handle the remaining tokens
    }

    (-$($t:tt)*) => {
        val -= 1;
        operate_integer_helper![$($t:tt)*]
    }
}

Это, конечно, не работает, происходит сбой компиляции со следующей ошибкой ( Playground ):

error: no rules expected the token `(`
   --> src/lib.rs:102:5
    |
102 |     (+$($t:tt)*) => {
    |     ^ no rules expected this token in macro call

Я застрял. Я знаю, что, возможно, мне не хватает многих концепций, так как я только начал, и я был бы очень признателен за помощь в понимании работы с макросами. Заранее спасибо!

1 Ответ

3 голосов
/ 04 мая 2019

Ты на самом деле очень близко!Осталась только пара мелких ошибок.( Если вы хотите узнать больше о макросах, читайте только один пункт за раз и попробуйте продвинуться оттуда самостоятельно! )

  • При использовании (повторяющиеся) мета-переменные, вы не указываете тип мета-переменной снова.Так что это $($t:tt)* в шаблоне макроса, но если вы хотите его использовать, это $($t)*!

  • Если у вас есть несколько правил в определении макроса, вам нужнозаканчивайте каждое правило точкой с запятой.

    macro_rules! {
        (+ $(t:tt)*) => { ... };
        (- $(t:tt)*) => { ... };
    }
    
  • Компилятору Rust всегда нужно знать, хотите ли вы расширить свой макрос в выражение или оператор (ы).Так как вы генерируете список операторов, а не одно выражение, вы должны добавить точку с запятой к вызову ваших макросов!Это означает, что в main() но также и во всех вызовах макросов вспомогательных макросов внутри определения макроса.

  • Поскольку вызов макроса создает новый синтаксический контекст и все идентификаторы(имена) доступны только в контексте синтаксиса, вспомогательный макрос не может использовать var (даже после исправления опечатки val -> var).Поэтому вместо этого вы должны передать это имя макросу-помощнику:

    macro_rules! operate_integer {
        ($($all_tokens:tt)*) => {
            let mut var: i32 = 0;
            operate_integer_helper![var $($all_tokens)*];  // <-- pass identifier 
            println!("{}", var);
        }
    }
    
    macro_rules! operate_integer_helper {
        ($var:ident +$($t:tt)*) => {              // <- accept identifier
            $var += 1;                            // <- use identifier
            operate_integer_helper![$var $($t)*]
        };
    
        ($var:ident -$($t:tt)*) => {
            $var -= 1;
            operate_integer_helper![$var $($t)*]
        };
    }
    
  • Сделав все это, вы получите ошибку «неожиданный конец вызова макроса».Это потому что у вас нет правила остановки рекурсии!Поэтому вам нужно добавить новое правило в ваш вспомогательный макрос: ($var:ident) => {};.Это правило используется, когда есть только имя и не осталось + или - токенов.

А теперь: работает!

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

С этим получится код .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...