我正在try 编写一个proc宏block!,它使用syn机箱来解析隐约类似JSX/RSX的语法. 以下内容应被解析成具有两个子块"text_1""text_2"的块"wrapper":

let block = block! {
    <"wrapper">
      <"text_1">("hello")
      <"text_2">("world")
};

在实现syn::parse时使用input.parse::<syn::Token![]>(),我可以很好地解析所有内容.

然而,有一件事我找不到解决方案:我想强制执行wrapper-children关系的代码样式,以便如果某个块是嵌套的,它必须在其前面有双倍空格或表格

// -- Good --
let block = block! {
  <"text_1">("hello")
  <"text_2">("world")
};

// -- Good --
let block = block! {
  <"wrapper">
    <"text_1">("hello")
    <"text_2">("world")
};

// -- Bad --
// Need spaces/tabs before the children tags
let block = block! {
  <"wrapper">
  <"text_1">("hello")
  <"text_2">("world")
};

通过Token![<]parse::<LitStr>()Token![>]parenthesized!bracketed!来解析TokenStream并不强制执行预期的规则,但我在syn个文档中找不到任何与解析制表符/空格相关的内容.

从控制台上打印的TokenStream来判断-它甚至没有第一个子元素的包装器><之间的任何东西

Input: TokenStream [
    Punct {
        ch: '<',
        spacing: Alone,
        span: #0 bytes(118..119),
    },
    Literal {
        kind: Str,
        symbol: "wrapper",
        suffix: None,
        span: #0 bytes(119..128),
    },
    Punct {
        ch: '>',
        spacing: Alone,
        span: #0 bytes(128..129),
    },
    Punct {
        ch: '<',
        spacing: Alone,
        span: #0 bytes(144..145),
    },
    Literal {
        kind: Str,
        symbol: "text_1",
        suffix: None,
        span: #0 bytes(145..153),
    },
    Punct {
        ch: '>',
        spacing: Alone,
        span: #0 bytes(153..154),
    },

有什么建议是可行的吗?如果可能的话

推荐答案

缩进在Rust的语法中并不重要,只需要偶尔使用空格来将单个令牌分成两个令牌(想想let name).因此,因为它唯一的功能目的是帮助解析令牌,所以空格本身并不是一个令牌.

如果您所需的宏语法为relies,则应改用字符串文字:

let block = block!(r#"
    <"wrapper">
        <"text_1">("hello")
        <"text_2">("world")
"#);

然而,有一种黑客方法可以获得你想要的东西,因为与令牌关联的Span有一个.source_text()方法.下面是一个简单的宏,它获取源代码并将其转换为文字:

use proc_macro::{Literal, TokenStream, TokenTree};

#[proc_macro]
pub fn block(tokens: TokenStream) -> TokenStream {
    let source = tokens
        .into_iter()
        .next()
        .unwrap()
        .span()
        .source_text()
        .unwrap();

    TokenTree::Literal(Literal::string(&source)).into()
}
use macros::block;

fn main() {
    let block = block! { {
        <"wrapper">
          <"text_1">("hello")
          <"text_2">("world")
    } };

    dbg!(block);
}
[src/main.rs:10] block = "{\n        <\"wrapper\">\n          <\"text_1\">(\"hello\")\n          <\"text_2\">(\"world\")\n    }"

然后,您可以逐行解析它,并比较您的过程宏中的前导空格.但请注意,我添加了额外的{ },因为TokenStream本身没有包含.span()可用.

您还应该注意文档中的说明,即这不是预期的用例:

返回范围后面的源文本.这将保留原始源代码,包括空格和注释.只有当跨度对应于真实源代码时,它才会返回结果.

注意:宏的可观察结果应该只依赖于标记,而不是这个源文本.此功能的结果是尽力仅用于诊断.

I do not encourage you to actually do this.和我相信你有很多好的意图,一个宏应该是强制风格.

Rust相关问答推荐

修改切片/引用数组

带参考文献的 rust 元组解构

不能在一个代码分支中具有不变的自身borrow ,而在另一个代码分支中具有可变的self borrow

解析程序无法在Cargo 发布中 Select 依赖版本

Rust,如何从 Rc> 复制内部值并返回它?

如何强制匹配的返回类型为()?

Rust Option 的空显式泛型参数

如何正确使用git2::Remote::push?

存储返回 impl Trait 作为特征对象的函数

在给定 Rust 谓词的情况下,将 Some 转换为 None 的惯用方法是什么?

由特征键控的不同 struct 的集合

Some(v) 和 Some(&v) 有什么区别?

如何使用泛型满足 tokio 异步任务中的生命周期界限

了解 Rust 闭包:为什么它们持续持有可变引用?

返回迭代器的特征

为什么可以在迭代器引用上调用 into_iter?

在异步 Rust 中,Future 如何确保它只调用最近的 Waker?

BigUint 二进制补码

在 Rust 中有条件地导入?

Rust:为什么在 struct 中borrow 引用会borrow 整个 struct?