編譯過(guò)程
整體流程:[源代碼]->分詞->[Tokens詞條流]->解析->[AST]->語(yǔ)法分析,宏擴(kuò)展→[高級(jí)中間語(yǔ)言HIR]->類型檢查->[中級(jí)中間語(yǔ)言MIR]->轉(zhuǎn)換->[LLVM IR]->LLVM->[目標(biāo)文件]->鏈接->[可執(zhí)行程序]
詳細(xì)過(guò)程如下:
- 解析輸入:將
.rs文件作為輸入并進(jìn)行解析生成AST(抽象語(yǔ)法樹(shù)); - 名稱解析,宏擴(kuò)展和屬性配置:解析完畢后處理
AST,處理#[cfg]節(jié)點(diǎn)解析路徑,擴(kuò)展宏; - 轉(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á); - 類型檢查以及后續(xù)分析:處理HIR的重要步驟就是類型檢查,例如使用
x.f時(shí)如果我們不知道x的類型就無(wú)法判斷訪問(wèn)的哪個(gè)f字段,類型檢查會(huì)創(chuàng)建TypeckTables其中包括表達(dá)式的類型,方法的解析方式; - 轉(zhuǎn)為MIR以及后置處理:完成類型檢查后,將HIR轉(zhuǎn)為MIR(中級(jí)中間表示)進(jìn)行借用檢查以及優(yōu)化;
- 轉(zhuǎn)為L(zhǎng)LVM IR和優(yōu)化:LLVM進(jìn)行優(yōu)化,從而生成許多
.o文件; - 鏈接: 最后將那些
.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,quote和proc_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)
}
}
}