[Rust] syn: Rust ์ฝ๋ ํ์
syn์ Rust ์ฝ๋ ์์ฒด์ ํ์ฑ์ ์ํ ์๋ํํฐ crate๋ค.
์ฝ๋๋ฅผ ๋ฐํ์์ด๋ ์ปดํ์ผํ์์ ๋ถ์ํด์ ๋ฉํํ๋ก๊ทธ๋๋ฐ์ด๋ ์ ๋๋ ์ด์
์ ์ํํ ํ์๊ฐ ์์ ๋ ์ ์ฉํ๋ค.
ํนํ, procedural macro์ ๋ณด์กฐ๋๊ตฌ๋ก ์ฌ์ฉํ๊ธฐ ์ข๋ค.
์ค์น
๋ฒ์ ์ 2๋ก ๋ฐ๊ณ , ์ํ๋ ๊ธฐ๋ฅ๋ง ํผ์ณ ํ๋๊ทธ์ ๋ฃ์ด์ค๋ค.
[dependencies]
syn = { version = "2", features = ["full"] }
๊ทธ๋ฆฌ๊ณ ํ๋๊ทธ๊ฐ ์ข ํผ๋์ค๋ฌ์ธ ์ ์๋๋ฐ, full์ ์ง์ง full์ด ์๋๋ค.
extra-traits ๊ฐ์ ๋ช๋ช ํ๋๊ทธ๋ ์์ผ๋ก ๋ฃ์ด์ค์ผ ํ๋ค. ์ ๊ฑธ ๋ฃ์ด์ผ Debug๊ฐ ๋๋ค.
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ์ ๋ณ๊ฒ ์๋ค.
๊ทธ๋ฅ ์ฝ๋ ๋ฃ๊ณ ๋๋ฆฌ๋ฉด AST๊ฐ ๋ฟ
๋์ค๋ ๊ฒ์ด๋ค.
const SOME_CODE: &str = r#"
struct Foo {
a: i32,
b: String,
}
fn main() {
println!("Hello, World!");
}
"#;
fn main() {
let ast = syn::parse_file(SOME_CODE).unwrap();
ast.items.iter().for_each(|item| match item {
syn::Item::Fn(f) => {
println!("Function: {}", f.sig.ident);
}
syn::Item::Struct(s) => {
println!("Struct: {}", s.ident);
s.fields.iter().for_each(|field| {
println!("Field: {}", field.ident.as_ref().unwrap());
});
}
_ => {}
});
}
๊ทธ๋ ๋ค.
์ด ๊ฒฝ์ฐ์๋ ์ ์ฒด ์ฝ๋๋ฅผ ๊ธฐ์ค์ผ๋ก ํ์ฑ์ ํ๋๋ฐ
parse_str์ ์ฐ๊ณ ์ด๋ค ํ์
์ผ๋ก ๋ฐ์์ง๋ฅผ ์ง์ ํ๋ฉด ํน์ ํํ์ ๋ํด์๋ง ๋ถ๋ถ์ ์ผ๋ก ํ์ฑ์ ํ ์๋ ์๋ค.
์๋๋ ๋จ์ผ ํํ์์ ํ์ฑํ๋ ๊ฐ๋จํ ์์ ๋ค.
use syn::Expr;
const SOME_CODE: &str = r#"
(1 + 20).to_string()
"#;
fn main() {
let ast: Expr = syn::parse_str(SOME_CODE).unwrap();
match ast {
Expr::MethodCall(method_call) => {
println!("Method call: {:?}", method_call.method);
match *method_call.receiver {
Expr::Paren(paren) => match *paren.expr {
Expr::Binary(binary) => {
if let Expr::Lit(lit) = *binary.left {
if let syn::Lit::Int(int) = lit.lit {
println!("Left: {:?}", int.base10_digits());
}
}
if let Expr::Lit(lit) = *binary.right {
if let syn::Lit::Int(int) = lit.lit {
println!("Right: {:?}", int.base10_digits());
}
}
}
_ => {
panic!("Not a binary");
}
},
_ => {
panic!("Not a paren");
}
}
}
_ => {
panic!("Not a method call");
}
}
}
์ ๋๋ค.
proc ๋งคํฌ๋ก์ ํจ๊ป ์ฌ์ฉํ๊ธฐ
์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ฐ๋ ฅํ ์ ์ค ํ๋๋, TokenStream์ ๋ํด์๋ ์ธ๋งํ๊ฒ ์ฌ๊ฐ๊ณต ๊ธฐ๋ฅ์ ์ ๊ณตํด์ค๋ค๋ ๊ฒ์ด๋ค.
procmacro๋ก ๋์ ์ ํด๋ณธ ์ฌ๋์ ์๊ฒ ์ง๋ง, TokenStream์ ํตํด์ ์ด๋์ ๋ ์ ๋ฆฌ๋ ํํ๋ก ์ฝ๋ ํํ๋ฅผ ๋ด๋ ค์ฃผ๊ธด ํ์ง๋ง, ์ง์ง ํ์ฑ๋ AST๊ฐ ์๋๋ผ lexing๋ง ์ฒ๋ฆฌ๋ ๊ฑฐ์ ์์ํ Token ๋ญ์น๋ผ์ ์์ฉํ๊ธฐ๊ฐ ๊น๋ค๋กญ๋ค.
์๋ฅผ ๋ค์ด foo: Option
syn์ ํ์ฑ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ์ด๊ฑธ ๊ฐ๋จํ๊ฒ AST ์์ค๊น์ง ๋ณํํ ์ ์๋ค.
๋ค์๊ณผ ๊ฐ์ด parse_macro_input ๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค. ์ด๊ฑด ๊ตฌ์กฐ์ฒด์ ๋ํด์ ์ฌ์ฉํ procmacro๋ค.
use proc_macro::TokenStream;
use syn::{parse_macro_input, ItemStruct};
#[proc_macro_attribute]
pub fn my_macro(_attribute: TokenStream, item: TokenStream) -> TokenStream {
let copied = item.clone();
// Parse the input tokens into a syntax tree
let ast = parse_macro_input!(item as ItemStruct);
println!("{:?}", ast);
// Hand the output tokens back to the compiler
TokenStream::from(copied)
}

๊ทธ๋ฆฌ๊ณ ๊ตฌ์กฐ์ฒด์ ๋ฌ์์ ๋๋ ค๋ณด๋ฉด

์ ๋นํ ์์๊ฒ ํ์ฑํด์ค ๊ฒ์ด๋ค.
๊ทธ๋ผ ๋ง๋ณด๊ณ ์ฆ๊ธฐ๋ฉด ๋๋ค.