Как разобрать одиночные токены в макросах ржавчины
Я начинаю играть с макросами 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 ответ
Вы на самом деле очень близко! Осталась только пара мелких ошибок. (Если вы хотите узнать больше о макросах, читайте только один пункт за раз и попробуйте продвинуться оттуда самостоятельно!)
При использовании (повторяющихся) метапеременных вы больше не указываете тип мета-переменной. Так что это
$($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) => {};
, Это правило используется, когда есть только имя и нет+
или же-
жетоны остались.
А теперь: это работает!
Я бы все-таки изменил одну последнюю вещь: обычно не очень хорошая идея иметь второй вспомогательный макрос, потому что этот макрос может не находиться в области видимости, где вызывается основной макрос. Вместо этого обычно используются внутренние правила. Вы можете прочитать больше о них здесь.
С этим, это результирующий код.