挑战LR解析

2020-09-17 00:50:52

这篇文章是对哪种解析方法的直接回应?如果你还没有读过那篇文章,现在就做吧 - 这是对现代解析技术领域的最好的短期调查。我同意这样的结论,如果你想“正确”地进行解析, - LR解析是可行的。几年前我也是这么想的:现代解析器生成器。

但是,这里需要注意的是,锈蚀分析器使用的是手工编写的递归下降/PRATT解析器,原因之一是我发现现有的LR解析器生成器不适合生产级编译器/IDE。在本文中,我想列出LR解析器生成器的作者面临的具体挑战。

我希望看到一个LR解析器,它生成以下语法树(来自Show SynTax Tree rust-Analyzer命令,为清晰起见省略了空格节点):

[email protected] [email protected][email protected]";fn";[email protected]@3..6";foo";[email protected][email protected]";(";[email protected] [email protected]";struct";[email protected] [email protected]&。[email protected] [email protected]";{";[email protected] [email protected] [email protected]";f";冒号@24..25";:";[email protected] [email protected] [email protected] [email protected] [email protected]";u32";[email protected]。)";

我知道的错误恢复能力最强的LR风格的解析器,tree sitter会生成这样的代码(tree sitter是GLR,这不是本文提倡的解析风格):

Source_file[0,0]-[5,0])Error[0,0]-[4,1])IDENTIFIER[0,3]-[0,6])struct_Pattern[2,0]-[4,1])type:TYPE_IDENTIFIER[2,0]-[2,6])Error[2,7]-[2,8])IDENTIFIER[2,7]-[2,8])field_Pattern[3,3]-[3。9])名称:FIELD_IDENTIFIER[3,3]-[3,4])模式:IDENTIFIER[3,6]-[3,9])。

Fn foo(有一个(不完整的)“function”节点。未用右括号括起来并不妨碍解析器识别参数列表。

例如,假设游标紧跟在(。如果我们有锈检分析器的语法树,那么我们就可以计算出我们正在完成一个函数参数。如果我们想要发现对(尚未完全编写的)foo的调用,则运行类型推断以确定第一个参数的类型,然后基于此建议参数名称和amp;类型(当前没有实现 - ,在锈检分析器中还有很多工作要做)。正确识别struct S对于以下方面很重要:1.如果我们有Ruust分析器的语法树,那么我们就可以判断出我们正在完成的是一个函数参数。如果我们想要找到对(尚未完全编写的)foo的调用,运行类型推断以确定第一个参数的类型,然后基于该类型建议参数名称和amp;type(目前没有实现Ruust分析器中还有很多工作要做)

关于LR解析器的错误恢复的文献很多,为什么学者们还没有弄清楚这一点呢?我有一个大胆的声明:学术界的错误恢复研究集中在一个与IDE无关的问题上,具体地说,研究的重点是寻找“最小代价修复序列”:

设计了一种算法,找出使当前文本解析的最小编辑。

这是一个非常适合学术界的问题, - 有一个精确的数学公式,有一个明显的强力解决方案(尝试所有的编辑),并且有足够的空间来寻找多项式算法。

但是IDE并不关心实际猜测和修复文本!它们只需要在现有文本中看到尽可能多的(可能不完整的)语法节点。

它不会认为“哦,我需要在这里插入)来完成参数列表”,相反,它看到struct后会想“哦,哇,没想到会这样!我想我就在这里停止解析参数列表“。

请注意,错误恢复能力是一个与错误报告正交的主题。我没有对错误报告给予太多关注(根据我的经验,在编辑器中同步报告语法错误可以补偿糟糕的语法错误消息),但MCR可能是一种很好的方法。

下一个挑战涉及表示运算符优先级和关联性。今天,标准解决方案是这样编写语法:

%START EXPR%%EXPR:EXPR";-";TERM|TERM;TERM:TERM";*";系数|系数;系数:";INT";;

我认为这对于机器来说是一个很好的解决方案,但是对于人类来说却是一个糟糕的UX。Rust有13个优先级 - ,我不可能想出13个不同的名称,比如Term和Factor。这里一个可读性更好的公式是优先表。有趣的是,这就是手写的Pratt解析器更具说明性的情况:

Fn infix_binding_power(op:char)->;option<;(U8,U8)>;{设res=匹配op{';=';=>;(2,1),';?';=>;(4,3),';+';|';-';=>;(5,6),';*';|';/';=>;(7,8),';=>;(14,13),_=>;不返回,};一些(分辨率)}。

最后,请提供像样的IDE支持^^以下是我认为简单而重要的功能:

一个稍微复杂但也很重要的特性是实时预览。它应该可以编辑语法或示例文本,并立即看到生成的解析树。如下所示:https://www.youtube.com/watch?v=gb1MJnTcvds&;feature=youtu.be(当然,更新应该是即时的)。对于UX,我建议使用doctest语法:

今天,实现一个基本的LSP服务器并让所有基本特性在大多数流行的编辑器中运行只需要一天的时间。实现实时预览会更复杂,因为没有现有的LSP请求。但是编写自定义扩展也不难,所以再增加一天的时间进行实时预览。