Макроси
23 ноември 2020
Макроси
Каква е целта им?
Макроси
Каква е целта им?
Генериране на код преди компилация
try!
Това вече сме го виждали, макар и за кратко
macro_rules! try {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(e) => return Err(e.into()),
}
}
}
try!
Това вече сме го виждали, макар и за кратко
macro_rules! try {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(e) => return Err(e.into()),
}
}
}
В новите версии на Rust се използва специалният синтаксис ?
Но това е добър пример за лесен макрос
add!
Общата схема
macro_rules! add {
($var1:expr, $var2:expr) => {
$var1 + $var2;
}
}
fn main() {
println!("{}", add!(1, 1));
println!("{}", add!("foo".to_string(), "bar"));
}
2 foobar
macro_rules! add { ($var1:expr, $var2:expr) => { $var1 + $var2; } } fn main() { println!("{}", add!(1, 1)); println!("{}", add!("foo".to_string(), "bar")); }
add!
Общата схема
add!
Общата схема
macro_rules!
всъщност не е макро, а е "syntax extension", имплементирано на ниво компилатор
add!
Общата схема
macro_rules!
всъщност не е макро, а е "syntax extension", имплементирано на ниво компилатор- Името на макроса следвано от чифт скоби за тялото на макроса:
macro_rules! add { ... }
add!
Общата схема
macro_rules!
всъщност не е макро, а е "syntax extension", имплементирано на ниво компилатор- Името на макроса следвано от чифт скоби за тялото на макроса:
macro_rules! add { ... }
- Аргументи в скоби, последвани от стрелкичка и още един чифт скоби:
(...) => { ... }
add!
Общата схема
macro_rules!
всъщност не е макро, а е "syntax extension", имплементирано на ниво компилатор- Името на макроса следвано от чифт скоби за тялото на макроса:
macro_rules! add { ... }
- Аргументи в скоби, последвани от стрелкичка и още един чифт скоби:
(...) => { ... }
- Всички тези скоби са взаимозаменяеми измежду кръгли, квадратни и къдрави скоби
add!
Общата схема
macro_rules!
всъщност не е макро, а е "syntax extension", имплементирано на ниво компилатор- Името на макроса следвано от чифт скоби за тялото на макроса:
macro_rules! add { ... }
- Аргументи в скоби, последвани от стрелкичка и още един чифт скоби:
(...) => { ... }
- Всички тези скоби са взаимозаменяеми измежду кръгли, квадратни и къдрави скоби
- "Променливите"
$var1
,$var2
се наричат метапроменливи и в случая са от "тип" expression - цялостен израз
add!
Защо не "променливи"? Защото в кръглите скоби се прави pattern-matching на ниво token-и:
macro_rules! add {
(Чш, я събери ($var1:expr) и ($var2:expr)) => {
$var1 + $var2;
}
}
fn main() {
println!("{}", add!(Чш, я събери (1) и (1)));
println!("{}", add!(Чш, я събери ("foo".to_string()) и ("bar")));
}
2 foobar
macro_rules! add { (Чш, я събери ($var1:expr) и ($var2:expr)) => { $var1 + $var2; } } fn main() { println!("{}", add!(Чш, я събери (1) и (1))); println!("{}", add!(Чш, я събери ("foo".to_string()) и ("bar"))); }
add!
Защо има скоби? За да се знае къде свършва expression/израз.
macro_rules! add {
(Чш, я събери $var1:expr и $var2:expr) => {
$var1 + $var2;
}
}
error: `$var1:expr` is followed by `и`, which is not allowed for `expr` fragments --> src/bin/main_be9c85d3e58c64ae0ab6a9978ece02a78cdf0563.rs:5:30 | 5 | (Чш, я събери $var1:expr и $var2:expr) => { | ^ not allowed after `expr` fragments | = note: allowed there are: `=>`, `,` or `;`
#![allow(unused_macros)] fn main () {} macro_rules! add { (Чш, я събери $var1:expr и $var2:expr) => { $var1 + $var2; } }
add!
Непосредствено след expr са позволени само (=>
), (,
) и (;
), ако expr не е в скоби
macro_rules! add {
(Чш, я събери $var1:expr, $var2:expr) => {
$var1 + $var2;
}
}
fn main() {
println!("{}", add!(Чш, я събери 1, 1));
println!("{}", add!(Чш, я събери "foo".to_string(), "bar"));
}
2 foobar
macro_rules! add { (Чш, я събери $var1:expr, $var2:expr) => { $var1 + $var2; } } fn main() { println!("{}", add!(Чш, я събери 1, 1)); println!("{}", add!(Чш, я събери "foo".to_string(), "bar")); }
map!
Нещо малко по-практично
macro_rules! map {
{
$( $key: expr : $value: expr ),*
} => {
// Забележете блока
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
map!
Какво прави $( ... ),*
?
map!
Какво прави $( ... ),*
?
- Това е repetition operator
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите:
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения+
- 1 или повече повторения
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения- Може да сложим разделител веднага след затварящата скоба например
,
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения- Може да сложим разделител веднага след затварящата скоба например
,
$( ... ),*
търси нещо от вида... , ... , ...
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения- Може да сложим разделител веднага след затварящата скоба например
,
$( ... ),*
търси нещо от вида... , ... , ...
- Операторът не поддържа optional trailing разделител
map!
Ок, нека да компилираме
macro_rules! map {
{
$( $key: expr : $value: expr ),*
} => {
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
map!
Ок, нека да компилираме
macro_rules! map {
{
$( $key: expr : $value: expr ),*
} => {
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
error: `$key:expr` is followed by `:`, which is not allowed for `expr` fragments --> src/bin/main_48aecfc760527ed5f683faae57a6895a4507076a.rs:6:23 | 6 | $( $key: expr : $value: expr ),* | ^ not allowed after `expr` fragments | = note: allowed there are: `=>`, `,` or `;`
#![allow(unused_macros)] fn main() {} macro_rules! map { { $( $key: expr : $value: expr ),* } => { { let mut map = ::std::collections::HashMap::new(); $( map.insert($key, $value); )* map } } }
map!
Правилата са си правила… Ще ги разгледаме подробно по-късно
macro_rules! map {
{
$( $key: expr => $value: expr ),*
} => {
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
#![allow(unused_macros)] fn main() {} macro_rules! map { { $( $key: expr => $value: expr ),* } => { { let mut map = ::std::collections::HashMap::new(); $( map.insert($key, $value); )* map } } }
map!
let m = map! {
"a" => 1,
"b" => 2
};
println!("{:?}", m);
{"a": 1, "b": 2}
macro_rules! map { { $( $key: expr => $value: expr ),* } => { { let mut map = ::std::collections::HashMap::new(); $( map.insert($key, $value); )* map } } } fn main() { let m = map! { "a" => 1, "b" => 2 }; println!("{:?}", m); }
map!
А какво става, ако искаме да поддържаме trailing comma 🤔
let m = map! {
"a" => 1,
"b" => 2,
};
println!("{:?}", m);
map!
А какво става, ако искаме да поддържаме trailing comma 🤔
let m = map! {
"a" => 1,
"b" => 2,
};
println!("{:?}", m);
error: unexpected end of macro invocation --> src/bin/main_5c32cc6affa79f7fd4a16843f01c100a2bb3f65e.rs:15:14 | 1 | macro_rules! map { | ---------------- when calling this macro ... 15 | "b" => 2, | ^ missing tokens in macro arguments
macro_rules! map { { $( $key: expr => $value: expr ),* } => { { let mut map = ::std::collections::HashMap::new(); $( map.insert($key, $value); )* map } } } fn main() { let m = map! { "a" => 1, "b" => 2, }; println!("{:?}", m); }
map!
Не точно каквото очаквахме..
map!
Може би така?
macro_rules! map {
{
$( $key: expr => $value: expr ),*,
} => {
/* ... */
}
}
let m = map! {
"a" => 1,
"b" => 2
};
map!
Може би така?
macro_rules! map {
{
$( $key: expr => $value: expr ),*,
} => {
/* ... */
}
}
let m = map! {
"a" => 1,
"b" => 2
};
error: unexpected end of macro invocation --> src/bin/main_069557a786e297d4c06486f2019e02e452864ede.rs:17:13 | 1 | macro_rules! map { | ---------------- when calling this macro ... 17 | "b" => 2 | ^ missing tokens in macro arguments
macro_rules! map { { $( $key: expr => $value: expr ),*, } => { /* ... */ { let mut map = ::std::collections::HashMap::new(); $( map.insert($key, $value); )* map } } } fn main() { let m = map! { "a" => 1, "b" => 2 }; }
map!
Не..
map!
Не бойте се, има си трик за това
Преди време се налагаше да правим следното, може да го видите в legacy код
macro_rules! map {
{
$( $key: expr => $value: expr ),* $(,)*
} => {
/* ... */
}
}
map!
Недостатъка е, че може да match-нем нещо такова
let m = map! {
"a" => 1,
"b" => 2,,,,,,,,,,,,
};
macro_rules! map { { $( $key: expr => $value: expr ),* $(,)* } => { { let mut map = ::std::collections::HashMap::new(); $( map.insert($key, $value); )* map } } } fn main() { let m = map! { "a" => 1, "b" => 2,,,,,,,,,,,, }; }
map!
Но както казахме има оператор ?
macro_rules! map {
{
$( $key: expr => $value: expr ),* $(,)?
} => {
/* ... */
}
}
map!
macro_rules! map {
{
$( $key: expr => $value: expr ),* $(,)?
} => {
/* ... */
}
}
map! {
"a" => 1,
"b" => 2
};
map! {
"a" => 1,
"b" => 2,
};
macro_rules! map { { $( $key: expr => $value: expr ),* $(,)? } => { /* ... */ { let mut map = ::std::collections::HashMap::new(); $( map.insert($key, $value); )* map } } } fn main() { map! { "a" => 1, "b" => 2 }; map! { "a" => 1, "b" => 2, }; }
Хигиена
Макросите в Rust са хигиенични
macro_rules! five_times {
($x:expr) => (5 * $x);
}
println!("{}", five_times!(2 + 3));
Хигиена
Макросите в Rust са хигиенични
macro_rules! five_times {
($x:expr) => (5 * $x);
}
println!("{}", five_times!(2 + 3));
25
macro_rules! five_times { ($x:expr) => (5 * $x); } fn main() { println!("{}", five_times!(2 + 3)); }
Хигиена
Макросите в Rust са хигиенични
macro_rules! five_times {
($x:expr) => (5 * $x);
}
println!("{}", five_times!(2 + 3));
25
macro_rules! five_times { ($x:expr) => (5 * $x); } fn main() { println!("{}", five_times!(2 + 3)); }
Нещо подобно в C/C++ би изчислило 13
Хигиена
В този пример отново заради хигиена двата state-а не се shadow-ват взаимно
macro_rules! log {
($msg:expr) => {{
let state: i32 = get_log_state();
if state > 0 {
println!("log({}): {}", state, $msg);
}
}};
}
let state: &str = "reticulating splines";
log!(state);
log(1): reticulating splines
fn get_log_state() -> i32 { 1 } macro_rules! log { ($msg:expr) => {{ let state: i32 = get_log_state(); if state > 0 { println!("log({}): {}", state, $msg); } }}; } fn main() { let state: &str = "reticulating splines"; log!(state); }
Хигиена
Всяко разгъване на макрос се случва в различен синтактичен контекст.
В този случай може да го мислите все едно двете променливи имат различен цвят който ги разграничава.
Хигиена
По тази причина не може да представяме нови променливи чрез макрос по следния начин
macro_rules! foo {
() => (let x = 3;);
}
foo!();
println!("{}", x);
error[E0425]: cannot find value `x` in this scope --> src/bin/main_f77bccafcffc077ab7e68cffcdade87e2b6d1674.rs:7:16 | 7 | println!("{}", x); | ^ not found in this scope
macro_rules! foo { () => (let x = 3;); } fn main() { foo!(); println!("{}", x); }
Хигиена
Ще трябва да подадем името на променлива на макроса за да се получи
macro_rules! foo {
($v:ident) => (let $v = 3;);
}
foo!(x);
println!("{}", x);
3
macro_rules! foo { ($v:ident) => (let $v = 3;); } fn main() { foo!(x); println!("{}", x); }
Хигиена
Правило важи за let
и цикли като loop while for
, но не и за item-и, което значи, че следното ще се компилира
macro_rules! foo {
() => (fn x() { println!("macros!") });
}
foo!();
x();
macros!
macro_rules! foo { () => (fn x() { println!("macros!") }); } fn main() { foo!(); x(); }
Синтаксис
Извикване на макроси
Макросите следват същите правила както останалата част от синтаксиса на Rust
Синтаксис
Извикване на макроси
Макросите следват същите правила както останалата част от синтаксиса на Rust
foo!( ... );
Синтаксис
Извикване на макроси
Макросите следват същите правила както останалата част от синтаксиса на Rust
foo!( ... );
foo![ ... ];
Синтаксис
Извикване на макроси
Макросите следват същите правила както останалата част от синтаксиса на Rust
foo!( ... );
foo![ ... ];
foo! { ... }
Синтаксис
Синтаксис
- Макросите трябва да съдържат само валидни Rust token-и
Синтаксис
- Макросите трябва да съдържат само валидни Rust token-и
- Скобите в макросите трябва да са балансирани т.е.
foo!([)
е невалидно
Синтаксис
- Макросите трябва да съдържат само валидни Rust token-и
- Скобите в макросите трябва да са балансирани т.е.
foo!([)
е невалидно - Без това ограничение, Rust няма как да знае къде свършва извикването на макроса
Синтаксис
Формално извикването на макрос се състои от поредица от token trees които са
Синтаксис
Формално извикването на макрос се състои от поредица от token trees които са
- произволна поредица от token trees обградена от
()
,[]
или{}
Синтаксис
Формално извикването на макрос се състои от поредица от token trees които са
- произволна поредица от token trees обградена от
()
,[]
или{}
- всеки друг единичен token
Синтаксис
Затова Rust макросите винаги приоритизират затварянето на скобите пред match-ването, което е полезно при някои подходи за match-ване
Синтаксис
Metavariables & Fragment specifiers
Tиповете на метапроменливите са
ident
: an identifier.x
;foo
path
: a qualified name.T::SpecialA
expr
: an expression.2 + 2
;if true { 1 } else { 2 }
;f(42)
ty
: a type.i32
;Vec<(char, String)>
;&T
pat
: a pattern.Some(t)
;(17, 'a')
;_
stmt
: a single statement.let x = 3
block
: a brace-delimited sequence of statements and optionally an expression.{ log(error, "hi"); return 12; }
item
: an item.fn foo() { }
;struct Bar;
meta
: a "meta item", as found in attributes.cfg(target_os = "windows")
tt
: a single token tree.lifetime
: a lifetime'a
.
Синтаксис
Metavariables & Fragment specifiers
Ограниченията за типовете са
expr
andstmt
variables may only be followed by one of:=> , ;
ty
andpath
variables may only be followed by one of:=> , = | ; : > [ { as where
pat
variables may only be followed by one of:=> , = | if in
- Other variables may be followed by any token.
Ръкави
Макросите могат да имат повече от един ръкав за matching разделени с ;
macro_rules! my_macro {
($e: expr) => (...);
($i: ident) => (...);
(for $i: ident in $e: expr) => (...);
}
Ръкави
Има и конвенция за private ръкави @text
, които да се викат чрез рекурсия
macro_rules! my_macro {
(for $i: ident in $e: expr) => (...);
(@private1 $e: expr) => (...);
(@private2 $i: ident) => (...);
}
Рекурсия
Макросите могат да извикват други макроси и дори себе си както този прост HTML shorthand
macro_rules! write_html {
($w: expr, ) => (());
($w: expr, $e: tt) => (write!($w, "{}", $e)?);
($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{
write!($w, "<{}>", stringify!($tag))?;
write_html!($w, $($inner)*);
write!($w, "</{}>", stringify!($tag))?;
write_html!($w, $($rest)*);
}};
}
#![allow(unused_macros)] fn main() {} macro_rules! write_html { ($w: expr, ) => (()); ($w: expr, $e: tt) => (write!($w, "{}", $e)?); ($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{ write!($w, "<{}>", stringify!($tag))?; write_html!($w, $($inner)*); write!($w, "{}>", stringify!($tag))?; write_html!($w, $($rest)*); }}; }
Рекурсия
use std::fmt::Write;
let mut out = String::new();
write_html! {
&mut out,
html[
head[title["Macros guide"]]
body[h1["Macros are the best!"]]
]
}
println!("{}", out);
<html><head><title>Macros guide</title></head><body><h1>Macros are the best!</h1></body></html>
macro_rules! write_html { ($w: expr, ) => (()); ($w: expr, $e: tt) => (write!($w, "{}", $e)?); ($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{ write!($w, "<{}>", stringify!($tag))?; write_html!($w, $($inner)*); write!($w, "{}>", stringify!($tag))?; write_html!($w, $($rest)*); }}; } fn main() -> Result<(), ::std::fmt::Error> { use std::fmt::Write; let mut out = String::new(); write_html! { &mut out, html[ head[title["Macros guide"]] body[h1["Macros are the best!"]] ] } println!("{}", out); Ok(()) }
Рекурсия
Може да срещнете legacy алтернатива на ?
, чрез използване на ръкави
macro_rules! map {
{ $( $key: expr => $value: expr ),*, } => {
map!( $( $key => $value ),* );
};
{ $( $key: expr => $value: expr ),* } => {
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
};
}
#![allow(unused_macros)] fn main() {} macro_rules! map { { $( $key: expr => $value: expr ),*, } => { map!( $( $key => $value ),* ); }; { $( $key: expr => $value: expr ),* } => { { let mut map = ::std::collections::HashMap::new(); $( map.insert($key, $value); )* map } }; }
Scoping
Компилатора разгъва макросите в ранна фаза на компилация, затова имат специфична видимост
Scoping
Компилатора разгъва макросите в ранна фаза на компилация, затова имат специфична видимост
- Дефинициите и разгръщанията се случват в едно depth-first lexical-order обхождане на crate-a
Scoping
Компилатора разгъва макросите в ранна фаза на компилация, затова имат специфична видимост
- Дефинициите и разгръщанията се случват в едно depth-first lexical-order обхождане на crate-a
- Затова видимостта на макрос е след дефиницията му - в същия scope и в child mods
Scoping
Компилатора разгъва макросите в ранна фаза на компилация, затова имат специфична видимост
- Дефинициите и разгръщанията се случват в едно depth-first lexical-order обхождане на crate-a
- Затова видимостта на макрос е след дефиницията му - в същия scope и в child mods
- Използването на макрос от друг модул става чрез
#[macro_use]
преди мястото, където го ползвате
Scoping
Имаме макроси дефинирани в macros и ще ги използваме в client
#[macro_use]
mod macros;
mod client; // ок
mod client; // компилационна грешка
#[macro_use]
mod macros;
Scoping
При работа на ниво crate
Scoping
При работа на ниво crate
- се използва
#[macro_use]
за импортиране на всичко или#[macro_use(my_macro, other_macro)]
Scoping
При работа на ниво crate
- се използва
#[macro_use]
за импортиране на всичко или#[macro_use(my_macro, other_macro)]
- за да направите макросите достъпни за други crate-ове се използва
#[macro_export]
Scoping
Имаме макроси дефинирани в macros и ще ги използваме в client
// crate macros
mod some_module {
#[macro_export]
macro_rules! hello {
() => (println!("Hello!"))
}
}
// crate client
#[macro_use]
extern crate macros;
fn main() {
hello!();
}
Scoping
От Rust 1.30 може и ето така
// crate macros
mod some_module {
#[macro_export]
macro_rules! hello {
() => (println!("Hello!"))
}
}
// crate client
// notice top-level use
use macros::hello;
fn main() {
hello!();
}
Scoping
Макроси дефинирани в блокове, функции или други подобни конструкции са видими само там
fn main() {
macro_rules! map { ... }
}
Scoping
Внимавайте с видимостта на типовет, които използвате
macro_rules! try {
($expr:expr) => {
match $expr {
$crate::result::Result::Ok(val) => val,
$crate::result::Result::Err(err) => {
return $crate::result::Result::Err($crate::convert::From::from(err));
}
}
};
($expr:expr,) => {
$crate::try!($expr)
};
}
#[allow(unused_macros)] macro_rules! try { ($expr:expr) => { match $expr { $crate::result::Result::Ok(val) => val, $crate::result::Result::Err(err) => { return $crate::result::Result::Err($crate::convert::From::from(err)); } } }; ($expr:expr,) => { $crate::try!($expr) }; } fn main() {}
Quality of life
Ако се чудите от къде да видите стандартните атрибуте - https://doc.rust-lang.org/stable/reference/attributes.html#macro-related-attributes
Debugging
Дебъгването на макроси е сложно, но има някои полезни команди
Debugging
Дебъгването на макроси е сложно, но има някои полезни команди
rustc --pretty expanded
Debugging
Дебъгването на макроси е сложно, но има някои полезни команди
rustc --pretty expanded
--pretty expanded,hygiene
за да се запазят syntax scope-овете
Debugging
Дебъгването на макроси е сложно, но има някои полезни команди
rustc --pretty expanded
--pretty expanded,hygiene
за да се запазят syntax scope-оветеcargo +nightly rustc -- -Z unstable-options --pretty=expanded
Debugging
Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly
Debugging
Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly
log_syntax!(...)
- принтира аргументите си при компилация на stdout и се разгръща до нищо
Debugging
Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly
log_syntax!(...)
- принтира аргументите си при компилация на stdout и се разгръща до нищоtrace_macros!(true)
- включва компилаторни съобщения при разгръщане на макрос
Debugging
Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly
log_syntax!(...)
- принтира аргументите си при компилация на stdout и се разгръща до нищоtrace_macros!(true)
- включва компилаторни съобщения при разгръщане на макросtrace_macros!(false)
- изключва съобщенията
Стандартни макроси
Стандартни макроси
panic!
- панира програмата
Стандартни макроси
panic!
- панира програматаvec!
- създава вектор от елементи
Стандартни макроси
panic!
- панира програматаvec!
- създава вектор от елементиassert!
&assert_eq!
- използват се при тестове за проверка на данните
Стандартни макроси
panic!
- панира програматаvec!
- създава вектор от елементиassert!
&assert_eq!
- използват се при тестове за проверка на данните- https://doc.rust-lang.org/stable/std/#macros
Advanced
TT Muncher (TokenTree Muncher или само Token Muncher)
macro_rules! write_html {
($w: expr, ) => (());
($w: expr, $e: tt) => (write!($w, "{}", $e)?);
($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{
write!($w, "<{}>", stringify!($tag))?;
write_html!($w, $($inner)*);
write!($w, "</{}>", stringify!($tag))?;
write_html!($w, $($rest)*);
}};
}
#![allow(unused_macros)] fn main() {} macro_rules! write_html { ($w: expr, ) => (()); ($w: expr, $e: tt) => (write!($w, "{}", $e)?); ($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{ write!($w, "<{}>", stringify!($tag))?; write_html!($w, $($inner)*); write!($w, "{}>", stringify!($tag))?; write_html!($w, $($rest)*); }}; }
Advanced
TT Muncher
write_html! {
&mut out,
html[
head[title["Macros guide"]]
body[h1["Macros are the best!"]]
]
}
Advanced
Push-Down Accumulation
Макрос, който инизиализира масив до 3 елемента
macro_rules! init_array {
[$e:expr; $n:tt] => {{
let e = $e;
init_array!(@accum ($n, e.clone()) -> ())
}};
(@accum (3, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (2, $e) -> ($($body)* $e,)) };
(@accum (2, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (1, $e) -> ($($body)* $e,)) };
(@accum (1, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (0, $e) -> ($($body)* $e,)) };
(@accum (0, $_e:expr) -> ($($body:tt)*)) => { init_array!(@as_expr [$($body)*]) };
(@as_expr $e:expr) => { $e };
}
let strings: [String; 3] = init_array![String::from("hi!"); 3];
println!("{:?}", strings);
["hi!", "hi!", "hi!"]
macro_rules! init_array { [$e:expr; $n:tt] => {{ let e = $e; init_array!(@accum ($n, e.clone()) -> ()) }}; (@accum (3, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (2, $e) -> ($($body)* $e,)) }; (@accum (2, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (1, $e) -> ($($body)* $e,)) }; (@accum (1, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (0, $e) -> ($($body)* $e,)) }; (@accum (0, $_e:expr) -> ($($body:tt)*)) => { init_array!(@as_expr [$($body)*]) }; (@as_expr $e:expr) => { $e }; } fn main() { let strings: [String; 3] = init_array![String::from("hi!"); 3]; println!("{:?}", strings); }
Advanced
Push-Down Accumulation
А не може ли да опростим нещата до това?
macro_rules! init_array {
(@accum 0, $_e:expr) => {/* empty */};
(@accum 1, $e:expr) => {$e};
(@accum 2, $e:expr) => {$e, init_array!(@accum 1, $e)};
(@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)};
[$e:expr; $n:tt] => {
{
let e = $e;
[ init_array!(@accum $n, e) ]
}
};
}
Advanced
Push-Down Accumulation
Не…
macro_rules! init_array {
(@accum 0, $_e:expr) => {/* empty */};
(@accum 1, $e:expr) => {$e};
(@accum 2, $e:expr) => {$e, init_array!(@accum 1, $e)};
(@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)};
[$e:expr; $n:tt] => {
{
let e = $e;
[ init_array!(@accum $n, e) ]
}
};
}
let strings: [String; 3] = init_array![String::from("hi!"); 3];
error: macro expansion ignores token `,` and any following --> src/bin/main_d7893d0d02408938f95722ad3842b5c665d42a1e.rs:5:31 | 5 | (@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)}; | ^ ... 9 | [ init_array!(@accum $n, e) ] | -------------------------- help: you might be missing a semicolon here: `;` | | | caused by the macro expansion here | = note: the usage of `init_array!` is likely invalid in expression context error[E0308]: mismatched types --> src/bin/main_d7893d0d02408938f95722ad3842b5c665d42a1e.rs:9:13 | 9 | [ init_array!(@accum $n, e) ] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 3 elements, found one with 1 element ... 15 | let strings: [String; 3] = init_array![String::from("hi!"); 3]; | ----------------------------------- in this macro invocation | = note: expected array `[String; 3]` found array `[String; 1]` = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
macro_rules! init_array { (@accum 0, $_e:expr) => {/* empty */}; (@accum 1, $e:expr) => {$e}; (@accum 2, $e:expr) => {$e, init_array!(@accum 1, $e)}; (@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)}; [$e:expr; $n:tt] => { { let e = $e; [ init_array!(@accum $n, e) ] } }; } fn main() { let strings: [String; 3] = init_array![String::from("hi!"); 3]; }
Advanced
Push-Down Accumulation
…защото това би довело до следното разгъване
init_array!(@accum 3, e)
e, init_array!(@accum 2, e)
e, e, init_array!(@accum 1, e)
e, e, e
[e, e, e]
Тук всяка помощна стъпка ще е невалиден Rust синтаксис и това не е позволено независимо от стъпките
Advanced
Push-Down Accumulation
Push-Down ни позволява да правим подобни конструкции чрез акумулиране на токени, без да се налага да имаме валиден синтаксис през цялото време.
Advanced
Push-Down Accumulation
Разгъвка на първия пример изглежда така
init_array! { String:: from ( "hi!" ) ; 3 }
init_array! { @ accum ( 3 , e . clone ( ) ) -> ( ) }
init_array! { @ accum ( 2 , e.clone() ) -> ( e.clone() , ) }
init_array! { @ accum ( 1 , e.clone() ) -> ( e.clone() , e.clone() , ) }
init_array! { @ accum ( 0 , e.clone() ) -> ( e.clone() , e.clone() , e.clone() , ) }
init_array! { @ as_expr [ e.clone() , e.clone() , e.clone() , ] }
Advanced
Push-Down Accumulation се използва в комбинация с TT Muncher, за да се парсват произволно сложни граматики