Rust编译器如何在泛型中标记'>'和'>>'?

时间:2019-09-23 15:18:30

标签: parsing compilation rust tokenize lexical-analysis

我已经编写了许多简单的标记器和递归下降解析器,因此我熟悉它们如何工作的基本概念。但是当我偶然发现以下Rust代码时,我感到很惊讶:

Option<Option<i32>>

我们知道Rust有一个>>移位运算符,所以我认为一个朴素的令牌生成器会在此处输出一个>>令牌,解析器会认为这是一个错误(因为它期望两个{{1 }}令牌。

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

  • 令牌生成器是否保持某种状态,以某种方式知道需要关闭尖括号?
  • 解析器是否检查>并将其分解为两个令牌,这些令牌又被推回到令牌流中?
  • 还是其他东西?

3 个答案:

答案 0 :(得分:5)

您可以查看Rust解析库以了解其处理方式。

库比较

模糊泡菜

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

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

完成标记化后,所有这些标记都将被收集到向量中,然后进行第二次解析。在此过程中,解析位置是allows being "split"的复杂索引。如果需要,解析器可以将>>分解为两个>。特定的解析函数根据要解析的内容寻找>>或两个递归>

标记和解析都是使用peresil条板箱实现的。

Syn

Syn是另一个解析库。在这里,他们使用了一个相关的想法:每个令牌是composed of multiple spans,每个字符一个。即,Shr结构具有一个spans: [Span; 2]字段。

Rustc

似乎编译器允许"gluing" multiple tokens into a bigger one。解析期间,>> 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

答案 1 :(得分:1)

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

现实是Rust令牌管道(tokenizer + lexer)是一个相对简单的递归下降解析器,具有超前功能(顺便说一句,它解释了您在编写错误代码时遇到的大量语法错误。例如,忘记了关闭方括号,解析器将卡在该块中,抱怨该块的限制)。每个令牌都会被提取,令牌之间的状态会保持不变,并且还会为预见目的而查看其他令牌。

当Rust遇到某种情况时,应该为其打开一个单独的状态(例如您的示例),该状态保持在状态中,以便能够精确地处理此状态。由于该语言构思精巧,因此在引用和引用调用之外没有真正的歧义(诸如*variable.call()之类的字眼-您是指(*variable).call()还是*(variable.call())?Rust让您明确指定了这一点)

当涉及到您所描述的类型定义时,没有任何歧义,因为移位运算符根据定义不能在该空间中。 Turbofish运算符也是如此-::的先例表明类型将是下一个。

因此,答案是“其他”-严格的词法分析器规则和有状态的解析器。

答案 2 :(得分:1)

词法分析器并不独立于解析器,因此它具有一些上下文。此外,关于您的确切问题,Rust类型只能在精确的位置找到:

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

  • :标记之后:再次没有歧义,因为冒号指示将要写入的类型:

    let x: Vec<_> = some_iterator.collect();
    
  • 在turbofish运算符中:

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

    该表示法仅用于不产生歧义。

  • 在特征依赖类型中:

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

    type关键字明确表明将存在一种类型。

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