2015-08-29 13 views
7

Tôi đang cố gắng tạo trình bao bọc cho macro. Vấn đề là tôi không muốn lặp lại các quy tắc tương tự trong cả hai macro. Có cách nào làm được việc này không?Làm cách nào để viết trình bao bọc cho macro mà không lặp lại quy tắc?

Đây là những gì tôi đã cố gắng:

macro_rules! inner { 
    ($test:ident) => { stringify!($test) }; 
    ($test:ident.run()) => { format!("{}.run()", stringify!($test)) }; 
} 

macro_rules! outer { 
    ($expression:expr) => { 
     println!("{}", inner!($expression)); 
    } 
} 

fn main() { 
    println!("{}", inner!(test)); 
    println!("{}", inner!(test.run())); 
    outer!(test); 
    outer!(test.run()); 
} 

nhưng tôi nhận được lỗi sau:

src/main.rs:8:31: 8:42 error: expected ident, found test 
src/main.rs:8   println!("{}", inner!($expression)); 
              ^~~~~~~~~~~ 

Nếu tôi thay đổi outer vĩ mô cho điều này, các biên dịch mã:

macro_rules! outer { 
    ($expression:expr) => { 
     println!("{}", stringify!($expression)); 
    } 
} 

Tôi đang làm gì sai?

Trả lời

7

macro_rules! là cả hai clevererdumber hơn bạn có thể nhận ra.

Ban đầu, tất cả đầu vào cho macro bắt đầu cuộc sống dưới dạng súp mã thông báo không phân biệt. An Ident tại đây, StrLit ở đó, vv Tuy nhiên, khi bạn kết hợp và nắm bắt một chút đầu vào, thông thường đầu vào sẽ được phân tích cú pháp trong nút Tóm tắt cú pháp cây; đây là trường hợp với expr.

Bit "thông minh" là khi bạn thay thế ảnh chụp này (ví dụ: $expression), bạn không chỉ thay thế mã thông báo ban đầu phù hợp: bạn thay thế toàn bộ nút AST thành một mã thông báo. Vì vậy, bây giờ có điều này không thực sự-a-token lạ trong đầu ra đó là một yếu tố toàn bộ cú pháp.

Bit "câm" là quá trình này về cơ bản không thể đảo ngược và chủ yếu là hoàn toàn vô hình. Vì vậy, chúng ta hãy lấy một ví dụ của bạn:

outer!(test); 

Chúng tôi chạy này thông qua một mức độ mở rộng, và nó trở thành này:

println!("{}", inner!(test)); 

Trừ, đó là không những gì nó trông như thế nào. Để làm cho mọi việc rõ ràng hơn, tôi sẽ phát minh ra một số cú pháp phi tiêu chuẩn:

println!("{}", inner!($(test):expr)); 

Giả vờ rằng $(test):expr là một dấu hiệu duy nhất: đó là một biểu hiện có thể được đại diện bởi các dãy thẻ test. Đó là không phải chỉ đơn giản là trình tự mã thông báo test. Đây là quan trọng, bởi vì khi người phiên dịch vĩ mô đi vào mở rộng mà inner! vĩ mô, nó sẽ kiểm tra quy tắc đầu tiên:

($test:ident) => { stringify!($test) }; 

Vấn đề là $(test):expr là một biểu hiện, không phải là một định danh. Có, nó chứa số nhận dạng, nhưng trình thông dịch macro không có vẻ sâu sắc. Nó thấy một biểu thức và chỉ từ bỏ.

Không khớp với quy tắc thứ hai cho cùng một lý do.

Vì vậy, bạn sẽ làm gì? ... Vâng, điều đó phụ thuộc. Nếu outer! không làm bất kỳ loại xử lý trên đầu vào của nó, bạn có thể sử dụng một khớp tt thay vì:

macro_rules! outer { 
    ($($tts:tt)*) => { 
     println!("{}", inner!($($tts)*)); 
    } 
} 

tt sẽ phù hợp với bất kỳ cây thẻ (xem Macros chapter of the Rust Book). $($tts:tt)* sẽ khớp với bất kỳ mã thông báo nào mà không thay đổi chúng. Đây là cách để chuyển tiếp một cách an toàn một loạt các mã thông báo sang một macro khác.

Nếu bạn cần xử lý trên dữ liệu nhập chuyển tiếp đến macro inner! ... có thể bạn sẽ phải lặp lại quy tắc.

Các vấn đề liên quan