rust 如何漂亮打印Syn AST?

mjqavswn  于 2022-11-12  发布在  其他
关注(0)|答案(4)|浏览(194)

我尝试使用syn从Rust文件创建一个AST,然后使用quote将其写入另一个文件。然而,当我写它时,它在所有内容之间添加了额外的空格。
请注意,下面的例子只是为了演示我遇到的最小可重复问题。我意识到,如果我只是想复制代码,我可以复制文件,但它不适合我的情况,我需要使用AST。

pub fn build_file() {
    let current_dir = std::env::current_dir().expect("Unable to get current directory");
    let rust_file = std::fs::read_to_string(current_dir.join("src").join("lib.rs")).expect("Unable to read rust file");
    let ast = syn::parse_file(&rust_file).expect("Unable to create AST from rust file");

    match std::fs::write("src/utils.rs", quote::quote!(#ast).to_string());
}

它创建AST的文件如下:


# [macro_use]

extern crate foo;
mod test;
fn init(handle: foo::InitHandle) {
    handle.add_class::<Test::test>();
}

它的输出是这样的:


# [macro_use] extern crate foo ; mod test ; fn init (handle : foo :: InitHandle) { handle . add_class :: < Test :: test > () ; }

我甚至尝试过在将它写入文件后通过rustfmt运行它,如下所示:

utils::write_file("src/utils.rs", quote::quote!(#ast).to_string());

match std::process::Command::new("cargo").arg("fmt").output() {
    Ok(_v) => (),
    Err(e) => std::process::exit(1),
}

但这似乎没有任何区别。

cgfeq70w

cgfeq70w1#

quote crate并不真正关心生成的代码的打印效果,你可以通过rustfmt运行它,你只需要执行rustfmt src/utils.rscargo fmt -- src/utils.rs

use std::fs;
use std::io;
use std::path::Path;
use std::process::Command;

fn write_and_fmt<P: AsRef<Path>, S: ToString>(path: P, code: S) -> io::Result<()> {
    fs::write(&path, code.to_string())?;

    Command::new("rustfmt")
        .arg(path.as_ref())
        .spawn()?
        .wait()?;

    Ok(())
}

现在您可以执行:

write_and_fmt("src/utils.rs", quote::quote!(#ast)).expect("unable to save or format");

另请参见Rust论坛上的"Any interest in a pretty-printing crate for Syn?"

0s7z1bwu

0s7z1bwu2#

正如Martin在他的回答中提到的,prettyplease可以用来格式化代码片段,这在测试proc宏时非常有用,因为proc_macro2::TokenStream上的标准to_string()很难读取。
下面是一个将proc_macro2::TokenStream解析为syn::Item的代码示例:

fn pretty_print_item(item: proc_macro2::TokenStream) -> String {
    let item = syn::parse2(item).unwrap();
    let file = syn::File {
        attrs: vec![],
        items: vec![item],
        shebang: None,
    };

    prettyplease::unparse(&file)
}

我在我的测试中使用了它来帮助我理解哪里是错误的生成代码:

assert_eq!(
    expected.to_string(),
    generate_event().to_string(),
    "\n\nActual:\n {}",
    pretty_print_item(generate_event())
);
xzv2uavs

xzv2uavs3#

请参阅新的prettyplease机箱。优点:
1.它可以直接用作库。
1.它可以处理代码片段,而rustfmt只能处理完整的文件。
1.它之所以快是因为它使用了更简单的算法。

vtwuwzda

vtwuwzda4#

与其他答案类似,我也使用prettyplease
我使用这个小技巧来漂亮地打印一个proc_macro2::TokenStream(例如,通过调用quote::quote!得到的结果):

fn pretty_print(ts: &proc_macro2::TokenStream) -> String {
    let file = syn::parse_file(&ts.to_string()).unwrap();
    prettyplease::unparse(&file)
}

基本上,我将令牌流转换为未格式化的String,然后将String解析为syn::File,然后将其传递给prettyplease包。

用法:


# [test]

fn it_works() {
    let tokens = quote::quote! {
        struct Foo {
            bar: String,
            baz: u64,
        }
    };

    let formatted = pretty_print(&tokens);
    let expected = "struct Foo {\n    bar: String,\n    baz: u64,\n}\n";

    assert_eq!(formatted, expected);
}

相关问题