【问题标题】:How do I use the Rust parser (libsyntax) myself?我如何自己使用 Rust 解析器(libsyntax)?
【发布时间】:2014-12-21 21:59:27
【问题描述】:

我想使用 Rust 解析器 (libsyntax) 来解析 Rust 文件并从中提取函数名称等信息。我开始深入研究文档和代码,所以我的第一个目标是开发一个程序,它可以在 .rs 文件中打印独立函数的所有函数名称。

程序应该在打印函数名称之前扩展所有宏,这样就不会错过通过宏声明的函数。这就是为什么我不能自己编写一些蹩脚的小解析器来完成这项工作。

我不得不承认我还不是很擅长编程 Rust,所以对于这个问题中的任何愚蠢陈述,我提前道歉。

我是怎么理解的,我需要做以下步骤:

  1. 通过Parser struct解析文件
  2. 使用MacroExpander 扩展宏
  3. ???
  4. 使用Visitor 遍历AST 并提取我需要的信息(例如,通过visit_fn

所以这是我的问题:

  1. 如何使用MacroExpander
  2. 如何使用自定义访问者遍历扩展的 AST?

我的想法是使用custom lint check 而不是完全成熟的解析器。我正在研究这个选项。

如果重要的话,我正在使用rustc 0.13.0-nightly (f168c12c5 2014-10-25 20:57:10 +0000)

【问题讨论】:

  • 提取有趣的事实通常需要的不仅仅是“只是一个解析器”请参阅我关于“解析后的生活”的文章(谷歌或通过生物页面)。

标签: parsing rust abstract-syntax-tree


【解决方案1】:

你可以使用syntex来解析Rust,所以你不需要使用不稳定的Rust。

这是一个简单的例子:

// Tested against syntex_syntax v0.33
extern crate syntex_syntax as syntax;

use std::rc::Rc;
use syntax::codemap::{CodeMap};
use syntax::errors::{Handler};
use syntax::errors::emitter::{ColorConfig};
use syntax::parse::{self, ParseSess};

fn main() {
    let codemap = Rc::new(CodeMap::new());
    let tty_handler =
        Handler::with_tty_emitter(ColorConfig::Auto, None, true, false, codemap.clone());
    let parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone());

    let src = "fn foo(x: i64) { let y = x + 1; return y; }".to_owned();

    let result = parse::parse_crate_from_source_str(String::new(), src, Vec::new(), &parse_session);
    println!("parse result: {:?}", result);
}

这会打印整个 AST:

parse result: Ok(Crate { module: Mod { inner: Span { lo: BytePos(0), hi: BytePos(43), expn_id: ExpnId(4294967295) },
items: [Item { ident: foo#0, attrs: [], id: 4294967295, node: Fn(FnDecl { inputs: [Arg { ty: type(i64), pat:
pat(4294967295: x), id: 4294967295 }], output: Default(Span { lo: BytePos(15), hi: BytePos(15), expn_id: ExpnId(4294967295) }),
variadic: false }, Normal, NotConst, Rust, Generics { lifetimes: [], ty_params: [], where_clause: WhereClause { id:
4294967295, predicates: [] } }, Block { stmts: [stmt(4294967295: let y = x + 1;), stmt(4294967295: return y;)], expr:
None, id: 4294967295, rules: Default, span: Span { lo: BytePos(15), hi: BytePos(43), expn_id: ExpnId(4294967295) } }),
vis: Inherited, span: Span { lo: BytePos(0), hi: BytePos(43), expn_id: ExpnId(4294967295) } }] }, attrs: [], config: [],
span: Span { lo: BytePos(0), hi: BytePos(42), expn_id: ExpnId(4294967295) }, exported_macros: [] })

【讨论】:

  • 我认为 syntex 不会继续。 issue
【解决方案2】:

恐怕我不能直接回答你的问题;但我可以提出一个可能有帮助的替代方案。

如果您只需要 AST,您可以使用 rustc -Z ast-json 以 JSON 格式检索它。然后使用你喜欢的语言(Python 很棒)来处理输出。

您还可以使用rustc --pretty=(expanded|normal|typed) 获得打印精美的源代码。

例如,给定这个hello.rs

fn main() {
    println!("hello world");
}

我们得到:

$ rustc -Z ast-json hello.rs
{"module":{"inner":null,"view_items":[{"node":{"va... (etc.)
$ rustc --pretty=normal hello.rs
#![no_std]
#[macro_use]
extern crate "std" as std;
#[prelude_import]
use std::prelude::v1::*;
fn main() { println!("hello world"); }
$ rustc --pretty=expanded hello.rs
#![no_std]
#[macro_use]
extern crate "std" as std;
#[prelude_import]
use std::prelude::v1::*;
fn main() {
    ::std::io::stdio::println_args(::std::fmt::Arguments::new({
                                                                  #[inline]
                                                                  #[allow(dead_code)]
                                                                  static __STATIC_FMTSTR:
                                                                         &'static [&'static str]
                                                                         =
                                                                      &["hello world"];
                                                                  __STATIC_FMTSTR
                                                              },
                                                              &match () {
                                                                   () => [],
                                                               }));
}

如果您需要更多功能,lint 插件将是最佳选择。正确处理宏扩展、config flags、模块系统以及出现的任何其他内容都非常重要。使用 lint 插件,您可以立即获得经过类型检查的 AST,而无需大惊小怪。 Cargo 也支持编译器插件,因此您的工具可以很好地融入其他人的项目。

【讨论】:

  • 谢谢!我也觉得不使用不稳定的编译器内部是要走的路!
【解决方案3】:

syn 板条箱确实有效。一开始我错误地认为它是用于编写程序宏(正如它的自述文件所暗示的那样),但它确实可以解析源代码文件。请看这个页面:https://docs.rs/syn/1.0.77/syn/struct.File.html。它甚至给出了一个输入 .rs 文件并输出 AST 的示例(当然,你可以用它做任何事情——不仅仅是打印):

use std::env;
use std::fs::File;
use std::io::Read;
use std::process;

fn main() {
    let mut args = env::args();
    let _ = args.next(); // executable name

    let filename = match (args.next(), args.next()) {
        (Some(filename), None) => filename,
        _ => {
            eprintln!("Usage: dump-syntax path/to/filename.rs");
            process::exit(1);
        }
    };

    let mut file = File::open(&filename).expect("Unable to open file");

    let mut src = String::new();
    file.read_to_string(&mut src).expect("Unable to read file");

    let syntax = syn::parse_file(&src).expect("Unable to parse file");

    // Debug impl is available if Syn is built with "extra-traits" feature.
    println!("{:#?}", syntax);
}

感谢@poolie 指出这个提示(虽然缺少一些细节)。

【讨论】:

    【解决方案4】:

    syntex 似乎不再维护(最后更新于 2017 年),但 https://crates.io/crates/syn 可能会满足您的需求。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-09
      相关资源
      最近更新 更多