Rust 编译器如何标记泛型中的“>”与“>>”?

How does the Rust compiler tokenize '>' vs '>>' in generics?

我写过很多简单的分词器和递归下降解析器,所以我熟悉它们工作原理的基本概念。但是当我偶然发现以下 Rust 代码时,我感到很惊讶:

Option<Option<i32>>

我们知道 Rust 有一个 >> 移位运算符,所以我认为天真的标记器会在这里输出一个 >> 标记,解析器会认为这是一个错误(因为它期望两个 >代币代替)。

但显然 Rust 编译器了解情况并正确处理。这是怎么回事?

实际上有一个问题非常详细地描述了其中的一些内容:#13: "The parser"

现实情况是,Rust 令牌管道(分词器 + 词法分析器)是一个相对简单的递归下降解析器,具有前瞻性(顺便说一句,这解释了当您编写不正确的代码时会遇到的大量语法错误。例如,忘记关闭括号,解析器将卡在那个块中,抱怨块的限制)。摄取每个令牌,在令牌之间保留状态,并查看额外的令牌以进行前瞻。

当 Rust 遇到它应该打开一个单独状态的东西时(比如你的例子),它被保存在状态中以便能够准确地处理这个。由于该语言的构思非常巧妙,除了引用和引用调用之外没有真正的歧义(比如 *variable.call() - 你是说 (*variable).call() 还是 *(variable.call())?Rust 让你明确指定) .

当谈到像您所描述的那样的类型定义时,没有歧义,因为根据定义,移位运算符不能在 space 中。 turbofish 运算符也是如此 - :: 先例表明类型将是下一个。

所以,答案是 "something else" - 严格的词法分析器规则和有状态的解析器。

词法分析器并不独立于解析器,所以它有一点上下文。此外,关于你的确切问题,Rust 类型只能在精确的地方找到:

  • 在函数签名中:显然,不能与运算符混淆。

  • : 印记之后:再次不能有任何歧义,因为冒号表示将写入的类型:

    let x: Vec<_> = some_iterator.collect();
    
  • 在涡轮鱼算子中:

    let x = some_iterator.collect::<Vec<_>>();
    

    该符号的存在只是为了避免产生歧义。

  • 在特质依赖类型中:

    impl trait Foo for Bar {
        type Dependent = Vec<u8>;
    }
    

    type关键字明确表示会有类型

如您所见,Rust 团队精心设计了语法,因此语法中不会存在歧义。

您可以查看 Rust 解析库以了解它们是如何处理的。

图书馆比较

毛茸茸的泡菜

这是我编写的解析器,所以我对其中的概念最为熟悉。

标记器是一个简单的逐字节解析器,它贪婪地 consumes the characters >> to create a DoubleRightAngle 标记。

标记化完成后,所有这些标记都会收集到一个向量中,然后进行第二次解析。在这一遍中,解析位置是一个复杂的索引allows being "split"。这允许解析器在需要时将 >> 分解为两个 >。特定的解析函数根据正在解析的内容查找 >> 或两个递归 >

标记化和解析都是使用 peresil crate 实现的。

同步

Syn 是另一个解析库。在这里,他们使用了一个相关的想法:每个标记都是 composed of multiple spans, one for each character. That is, the Shr 结构,有一个 spans: [Span; 2] 字段。

Rustc

看来编译器允许 "gluing" multiple tokens into a bigger one. During parsing, >> can be "consumed" and replaced with a >:

token::BinOp(token::Shr) => {
    let span = self.token.span.with_lo(self.token.span.lo() + BytePos(1));
    Some(self.bump_with(token::Gt, span))
}
token::BinOpEq(token::Shr) => {
    let span = self.token.span.with_lo(self.token.span.lo() + BytePos(1));
    Some(self.bump_with(token::Ge, span))
}
token::Ge => {
    let span = self.token.span.with_lo(self.token.span.lo() + BytePos(1));
    Some(self.bump_with(token::Eq, span))
}

加分

空格周围还有一个问题。解析器应该等效地解析这两种类型:

Option<Option<i32>>
Option < Option < i32 > >

但是,它不应该等效地解析这些表达式:

a >>= 1
a >> = 1