Rust之Macro

編譯過(guò)程

整體流程:[源代碼]->分詞->[Tokens詞條流]->解析->[AST]->語(yǔ)法分析,宏擴(kuò)展→[高級(jí)中間語(yǔ)言HIR]->類型檢查->[中級(jí)中間語(yǔ)言MIR]->轉(zhuǎn)換->[LLVM IR]->LLVM->[目標(biāo)文件]->鏈接->[可執(zhí)行程序]

詳細(xì)過(guò)程如下:

  1. 解析輸入:將.rs文件作為輸入并進(jìn)行解析生成AST(抽象語(yǔ)法樹(shù));
  2. 名稱解析,宏擴(kuò)展和屬性配置:解析完畢后處理AST,處理#[cfg]節(jié)點(diǎn)解析路徑,擴(kuò)展宏;
  3. 轉(zhuǎn)為HIR:名稱解析完畢后將AST轉(zhuǎn)換為HIR(高級(jí)中間表示),HIR比AST處理的更多,但是他不負(fù)責(zé)解析Rust的語(yǔ)法,例如((1+2)+3)1+2+3在AST中會(huì)保留括號(hào),雖然兩者的含義相同但是會(huì)被解析成不同的樹(shù),但是在HIR中括號(hào)節(jié)點(diǎn)將會(huì)被刪除,這兩個(gè)表達(dá)式會(huì)以相同的方式表達(dá);
  4. 類型檢查以及后續(xù)分析:處理HIR的重要步驟就是類型檢查,例如使用x.f時(shí)如果我們不知道x的類型就無(wú)法判斷訪問(wèn)的哪個(gè)f字段,類型檢查會(huì)創(chuàng)建TypeckTables其中包括表達(dá)式的類型,方法的解析方式;
  5. 轉(zhuǎn)為MIR以及后置處理:完成類型檢查后,將HIR轉(zhuǎn)為MIR(中級(jí)中間表示)進(jìn)行借用檢查以及優(yōu)化;
  6. 轉(zhuǎn)為L(zhǎng)LVM IR和優(yōu)化:LLVM進(jìn)行優(yōu)化,從而生成許多.o文件;
  7. 鏈接: 最后將那些.o文件鏈接在一起。

Rust在預(yù)處理時(shí),會(huì)加入以下代碼:

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;

從而自動(dòng)導(dǎo)入標(biāo)準(zhǔn)庫(kù)。

聲明宏(Declarative Macro)

宏解析器將宏擴(kuò)展的時(shí)機(jī)在解析過(guò)程中。

聲明宏中可以捕獲的類型:

  • item,語(yǔ)言項(xiàng),比如模塊、聲明、函數(shù)定義、類型定義、結(jié)構(gòu)體定義、impl實(shí)現(xiàn)等。
  • block,代碼塊,由花括號(hào)限定的代碼;
  • stmt,語(yǔ)句,一般是指以分號(hào)結(jié)尾的代碼;
  • expr,表達(dá)式,會(huì)生成具體的值;
  • pat,模式;
  • ty,類型;
  • ident,標(biāo)識(shí)符;
  • path,路徑,比如foo、std::iter等;
  • meta,元信息,包含在#[...]或者#![...]屬性內(nèi)的信息;
  • tt,TokenTree的縮寫(xiě),詞條樹(shù);
  • vis,可見(jiàn)性,比如pub;
  • lifetime,生命周期參數(shù)。

詞法樹(shù)的范圍比表達(dá)式的范圍廣,比如匹配一個(gè)語(yǔ)句塊時(shí),就必須用tt。

重復(fù)匹配的模式是“$(...) sep rep”,具體的說(shuō)明如下:

  • $(...),代表要把重復(fù)匹配的模式置于其中;
  • sep,代表分隔符,常用逗號(hào)、分號(hào)和=>。這個(gè)分隔符可以依據(jù)具體的情況忽略。
  • rep,代表控制重復(fù)次數(shù)的標(biāo)記,目前支持兩種:* 和 +,代表“重復(fù)零次及以上”和“重復(fù)一次及以上”。

展開(kāi)宏:

cargo rustc -- -Z unstable-options --pretty=expanded

rustc -Z unstable-options --pretty=expanded main.rs
#[macro_export]
macro_rules! my_vec { 
  // x 為重復(fù)匹配到的表達(dá)式,“,”可以根據(jù)情況忽略
    ($($x: expr), *) => {
        {
            let mut temp_vec = Vec::new();
          // 重復(fù)匹配到的值在這里訪問(wèn)
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

hashmap實(shí)現(xiàn)

遞歸調(diào)用

#![allow(unused)]
macro_rules! hashmap {
  // 利用遞歸調(diào)用消去最后鍵值對(duì)的結(jié)尾逗號(hào)
  // 如果是末尾仍有逗號(hào)的,會(huì)轉(zhuǎn)換成($($key:expr => $value:expr),*)
    ($($key:expr => $value:expr,)*) =>
        {  hashmap!($($key => $value),*) };
    ($($key:expr => $value:expr),* ) => {
        {
            let mut _map = ::std::collections::HashMap::new();
            $(
                _map.insert($key, $value);
            )*
           _map
       }
   };
}
fn main(){
    let map = hashmap!{
        "a" => 1,
        "b" => 2,
        "c" => 3, 
    };
    assert_eq!(map["a"], 1);
}

第一層轉(zhuǎn)換為hashmap ! ("a" => 1, "b" => 2, "c" => 3)。

重復(fù)匹配規(guī)則

macro_rules! hashmap {
    ($($key:expr => $value:expr),* $(,)*) => {
        {
            let mut _map = ::std::collections::HashMap::new();
            $(
                _map.insert($key, $value);
            )*
            _map
        }
   };
}
fn main(){
    let map = hashmap!{
        "a" => 1,
        "b" => 2,
        "c" => 3, 
    };
    assert_eq!(map["a"], 1);
}

預(yù)分配空間

macro_rules! unit {
    ($($x:tt)*) => (());
}
macro_rules! count {
    ($($key:expr),*) => (<[()]>::len(&[$(unit!($key)),*]));
}
macro_rules! hashmap {
    ($($key:expr => $value:expr),* $(,)*) => {
        {
           let _cap = count!($($key),*);
           let mut _map 
               = ::std::collections::HashMap::with_capacity(_cap);
           $(
               _map.insert($key, $value);
           )*
           _map
       }
   };
}

消除外部宏

#![feature(trace_macros)]
macro_rules! hashmap {
    (@unit $($x:tt)*) => (());
    (@count $($rest:expr),*) => 
        (<[()]>::len(&[$(hashmap!(@unit $rest)),*]));
    ($($key:expr => $value:expr),* $(,)*) => {
        {
            let _cap = hashmap!(@count $($key),*);
            let mut _map = 
                ::std::collections::HashMap::with_capacity(_cap);
           $(
               _map.insert($key, $value);
           )*
           _map
       }
   };
}
fn main(){
   trace_macros!(true);
   let map = hashmap!{
       "a" => 1,
       "b" => 2,
       "c" => 3, 
   };
   assert_eq!(map["a"], 1);
}

#![feature(trace_macros)]在nightly版本下可以跟蹤宏展開(kāi),在需要展開(kāi)宏的地方使用trace_macros!(true);打開(kāi)跟蹤。

導(dǎo)入/導(dǎo)出

#[macro_export]表示下面的宏定義對(duì)其他包也是可見(jiàn)的。#[macro_use]可以導(dǎo)入宏。

在宏定義中使用$crate,可以在被導(dǎo)出時(shí),讓編譯器根據(jù)上下文推斷包名,避免依賴問(wèn)題。

過(guò)程宏

過(guò)程宏的Cargo.toml中,設(shè)置lib類型:

[lib]
proc_macro = true

自定義派生屬性

derive屬性,自動(dòng)為結(jié)構(gòu)體或枚舉類型進(jìn)行語(yǔ)法擴(kuò)展??梢允褂肨DD(測(cè)試驅(qū)動(dòng)開(kāi)發(fā))的方式來(lái)開(kāi)發(fā)。

包結(jié)構(gòu):

|-Cargo.toml
|-src
    |- lib.rs
|-tests
    |- test.rs

先寫(xiě)test.rs

#[macro_use]
extern crate my_proc_macro;

#[derive(A)]
struct A;
#[test]
fn test_derive_a(){
    assert_eq!("hello from impl A".to_string(), A.a());
}

再寫(xiě)lib.rs

extern crate proc_macro;
use self::proc_macro::TokenStream;

// 自定義派生屬性
#[proc_macro_derive(A)]
pub fn derive(input: TokenStream) -> TokenStream {
    let _input = input.to_string();
    assert!(_input.contains("struct A;"));
    r#"
        impl A {
            fn a(&self) -> String{
               format!("hello from impl A")
           }
       }
   "#.parse().unwrap()
}

自定義屬性

可以說(shuō)自定義派生屬性是自定義屬性的特例。例如條件編譯屬性#[cfg()]和測(cè)試屬性#[test]都是自定義屬性。

test.rs

use my_proc_macro::attr_with_args;
#[attr_with_args("Hello, Rust!")]
fn foo(){}
#[test]
fn test_foo(){
    assert_eq!(foo(), "Hello, Rust!");
}

lib.rs

extern crate proc_macro;
use self::proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn attr_with_args(args: TokenStream, input: TokenStream)
                      -> TokenStream {
    let args = args.to_string();
    let _input = input.to_string();
    format!("fn foo() -> &'static str {% raw %}{{ {} }}{% endraw %}", args)
        .parse().unwrap()
}

在引號(hào)的括號(hào)要用{% raw %}{{{% endraw %}轉(zhuǎn)義,最內(nèi)部的{}是給args占位的。

Bang宏/類函數(shù)宏

test.rs

#![feature(proc_macro_hygiene)]
use my_proc_macro::hashmap;
#[test]
fn test_hashmap(){
    let hm = hashmap!{ "a":1,"b":2,};
    assert_eq!(hm["a"],1);
    let hm = hashmap!{"a"=>1,"b"=>2,"c"=>3};
    assert_eq!(hm["c"],3);
}

lib.rs

#[proc_macro]
pub fn hashmap(input: TokenStream) -> TokenStream {
    // 轉(zhuǎn)換input為字符串
    let _input = input.to_string();
    // 將input字符串結(jié)尾的逗號(hào)去掉,否則在下面迭代中將報(bào)錯(cuò)
    let input = _input.trim_end_matches(',');
    // 用split將字符串分割為slice,然后用map去處理
    // 為了支持「"a" : 1」或 「"a" => 1」這樣的語(yǔ)法
    let input: Vec<String> = input.split(",").map(|n| {
        let mut data = if n.contains(":") {  n.split(":") }
                       else { n.split(" => ") };
        let (key, value) =
           (data.next().unwrap(), data.next().unwrap());
       format!("hm.insert({}, {})", key, value)
    }).collect();
    let count: usize = input.len();
    let tokens = format!("
        {% raw %}{{
        let mut hm =
            ::std::collections::HashMap::with_capacity({});
            {}
            hm
        }}{% endraw %}", count,
        input.iter().map(|n| format!("{};", n)).collect::<String>()
    );
    // parse函數(shù)會(huì)將字符串轉(zhuǎn)為Result<TokenStream>
    tokens.parse().unwrap()
}

第三方包

使用syn,quoteproc_macro可以實(shí)現(xiàn)自定義派生屬性功能。

以下代碼實(shí)現(xiàn)了派生屬性New

extern crate proc_macro;
use {
    syn::{Token, DeriveInput, parse_macro_input},
    quote::*,
    proc_macro2,
    self::proc_macro::TokenStream,
};

#[proc_macro_derive(New)]
pub fn derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let result = match ast.data {
        syn::Data::Struct(ref s) => new_for_struct(&ast, &s.fields),
        _ => panic!("doesn't work with unions yet"),
    };
    result.into()
}

fn new_for_struct(ast: &syn::DeriveInput,fields: &syn::Fields) -> proc_macro2::TokenStream
{
    match *fields {
        syn::Fields::Named(ref fields) => {
            new_impl(&ast, Some(&fields.named), true)
        },
        syn::Fields::Unit => {
            new_impl(&ast, None, false)
        },
        syn::Fields::Unnamed(ref fields) => {
            new_impl(&ast, Some(&fields.unnamed), false)
        },
    }
}

fn new_impl(ast: &syn::DeriveInput,
            fields: Option<&syn::punctuated::Punctuated<syn::Field, Token![,]>>,
            named: bool) -> proc_macro2::TokenStream
{
    let struct_name = &ast.ident;

    let unit = fields.is_none();
    let empty = Default::default();

    let fields: Vec<_> = fields.unwrap_or(&empty)
        .iter()
        .enumerate()
        .map(|(i, f)| FieldExt::new(f, i, named)).collect();

    let args = fields.iter().map(|f| f.as_arg());
    let inits = fields.iter().map(|f| f.as_init());

    let inits = if unit {
        quote!()
    } else if named {
        quote![ { #(#inits),* } ]
    } else {
        quote![ ( #(#inits),* ) ]
    };


    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
    let (new, doc) = (
        syn::Ident::new("new", proc_macro2::Span::call_site()),
        format!("Constructs a new `{}`.", struct_name)
    );
    quote! {
        impl #impl_generics #struct_name #ty_generics #where_clause {
            #[doc = #doc]
            pub fn #new(#(#args),*) -> Self {
                #struct_name #inits
            }
        }
    }
}

struct FieldExt<'a> {
    ty: &'a syn::Type,
    ident: syn::Ident,
    named: bool,
}
impl<'a> FieldExt<'a> {
    pub fn new(field: &'a syn::Field, idx: usize, named: bool) -> FieldExt<'a> {
        FieldExt {
            ty: &field.ty,
            ident: if named {
                field.ident.clone().unwrap()
            } else {
                syn::Ident::new(&format!("f{}", idx), proc_macro2::Span::call_site())
            },
            named: named,
        }
    }
    pub fn as_arg(&self) -> proc_macro2::TokenStream {
        let f_name = &self.ident;
        let ty = &self.ty;
        quote!(#f_name: #ty)
    }

    pub fn as_init(&self) -> proc_macro2::TokenStream {
        let f_name = &self.ident;
        let init =  quote!(#f_name);
        if self.named {
            quote!(#f_name: #init)
        } else {
            quote!(#init)
        }
    }
}

參考

使用Rust開(kāi)發(fā)編譯系統(tǒng)(C以及Rust編譯的過(guò)程)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • //Clojure入門(mén)教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語(yǔ)閱讀 4,036評(píng)論 0 7
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom閱讀 3,156評(píng)論 0 3
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,626評(píng)論 1 32
  • 官網(wǎng) 中文版本 好的網(wǎng)站 Content-type: text/htmlBASH Section: User ...
    不排版閱讀 4,701評(píng)論 0 5
  • 邑外東風(fēng)初度,聆佳訊、蕊淡枝斜。 誰(shuí)解蘭心香一縷。清思幾句? 筆凝云,詞入畫(huà),只怕,填罷, 輕易春別。 (上行杯,...
    君子包閱讀 257評(píng)論 2 8

友情鏈接更多精彩內(nèi)容