Parsing 哪些语法可以使用递归下降而无需回溯来解析?

Parsing 哪些语法可以使用递归下降而无需回溯来解析?,parsing,lua,context-free-grammar,ll,recursive-descent,Parsing,Lua,Context Free Grammar,Ll,Recursive Descent,根据,无回溯的递归下降(也称为预测性解析)仅适用于LL(k)语法 在其他地方,我读到Lua的实现使用了这样一个解析器。但是,语言不是LL(k)。事实上,Lua本质上是模糊的:a=f(g)(h)[i]=1meana=f(g);(h) [i]=1或a=f;(g) (h)[i]=1?这种歧义是通过解析器中的贪婪来解决的(因此上述内容被解析为错误的a=f(g)(h)[i];=1) 这个例子似乎表明预测解析器可以处理非LL(k)的语法。事实上,他们真的可以处理LL(k)的超集吗?如果是的话,有没有办法确定

根据,无回溯的递归下降(也称为预测性解析)仅适用于LL(k)语法

在其他地方,我读到Lua的实现使用了这样一个解析器。但是,语言不是LL(k)。事实上,Lua本质上是模糊的:a=f(g)(h)[i]=1mean
a=f(g);(h) [i]=1
a=f;(g) (h)[i]=1
?这种歧义是通过解析器中的贪婪来解决的(因此上述内容被解析为错误的
a=f(g)(h)[i];=1

这个例子似乎表明预测解析器可以处理非LL(k)的语法。事实上,他们真的可以处理LL(k)的超集吗?如果是的话,有没有办法确定给定的语法是否在这个超集中

换句话说,如果我正在设计一种我想使用预测解析器解析的语言,我是否需要将该语言限制为LL(k)?或者我可以申请更宽松的限制?

TL;博士 对于递归下降解析器的适当定义,递归下降只能解析LL(k)语言是绝对正确的

Lua可以用递归下降解析器解析,因为语言是LL(k);也就是说,Lua存在LL(k)语法。[注1]

1.LL(k)语言可能有非LL(k)语法。 如果存在识别语言的LL(k)语法,那么一种语言就是LL(k)。这并不意味着每一个识别语言的语法都是LL(k);可能有任意数量的非LL(k)语法可以识别该语言。因此,一些语法不是LL(k)的事实完全不能说明语言本身

2.许多实用的编程语言都是用模糊语法描述的。 在形式语言理论中,只有当语言的每一个语法都是模糊的时,语言才是模糊的。可以肯定地说,没有一种实用的编程语言本质上是模棱两可的,因为实用的编程语言是(以某种方式)被确定地解析的。[注2]

由于编写严格意义上的非歧义语法可能会很乏味,因此语言文档通常会提供歧义语法,以及指示如何解决歧义的文本材料

例如,许多语言(包括Lua)都使用一种语法进行记录,该语法不明确包含运算符优先级,允许对表达式使用一个简单的规则:

exp ::= exp Binop exp | Unop exp | term
该规则显然是不明确的,但给定一个操作符列表、它们的相对先例以及每个操作符是左关联还是右关联的指示,该规则可以机械地扩展为明确的表达式语法。实际上,许多解析器生成器允许用户单独提供优先级声明,并在生成解析器的过程中执行机械扩展。应该注意的是,生成的解析器是消歧语法的解析器,因此原始语法的歧义并不意味着解析算法能够处理歧义语法

另一个可以机械消除歧义的歧义引用语法的常见例子是在C语言(但不是Lua语言)中发现的歧义。语法:

if-statement ::= "if" '(' exp ')' stmt
               | "if" '(' exp ')' stmt "else" stmt
当然是模棱两可的,;这样做的目的是让解析变得“贪婪”。同样,这种模糊性不是固有的。有一种机械转换,它产生一种明确的语法,类似于以下内容:

matched-statement ::= matched-if-stmt | other-statement
statement         ::= matched-if-stmt | unmatched-if-stmt
matched-if-stmt   ::= "if" '(' exp ')' matched-statement "else" matched-statement 
unmatched-if-stmt ::= "if" '(' exp ')' statement
                    | "if" '(' exp ')' matched-statement "else" unmatched-if-stmt
解析器生成器隐式执行此转换是非常常见的。(对于LR解析器生成器,如果reduce操作与shift操作冲突,转换实际上是通过删除reduce操作来实现的。这比转换语法简单,但效果完全相同。)

所以Lua(和其他编程语言)并不是天生的模棱两可;因此,它们可以通过解析算法进行解析,而解析算法需要明确的确定性解析器。事实上,有些语言的所有可能语法都是模棱两可的,这甚至可能有点令人惊讶。正如上面引用的维基百科文章所指出的那样,罗希特·帕里克在1961年证明了这种语言的存在;本质上不明确的上下文无关语言的一个简单示例是

{anbmcmdn | n,m≥0} ∪ {anbncmdm | n,m≥0}

3.贪婪LL(1)解析Lua赋值和函数调用语句 与上面的悬挂else构造一样,Lua语句序列的消歧是通过只允许贪婪解析来执行的。直觉上,程序是直接的;它基于禁止两个连续语句(不插入分号),其中第二个语句以一个令牌开头,该令牌可能会继续第一个语句

在实践中,其实没有必要进行这种转变;它可以在构造解析器的过程中隐式完成。所以我不想费心在这里生成一个完整的Lua语法。但我相信这里的Lua语法的一小部分足以说明转换是如何工作的

以下子集(主要基于参考语法)正好显示了OP中指出的歧义:

program        ::= statement-list
statement-list ::= Ø
                 | statement-list statement
statement      ::= assignment | function-call | block | ';'
block          ::= "do" statement-list "end"
assignment     ::= var '=' exp
exp            ::= prefixexp                          [Note 3]
prefixexp      ::= var | '(' exp ')' | function-call
var            ::= Name | prefixexp '[' exp ']'
function-call  ::= prefixexp '(' exp ')'
(注意:(我使用
Ø
表示空字符串,而不是
ε
λ
,或
%empty

Lua语法as是左递归的,因此它显然不是LL(k)(独立于歧义性)。删除左递归可以机械地完成;为了证明子集是LL(1),我在这里已经做了足够多的工作。不幸的是,转换后的语法没有保留解析树的结构,这是LL(k)语法的一个典型问题。在递归下降解析过程中重建正确的解析树通常很简单,我不打算详细介绍

p是很简单的
exp          ::= term exp-postfix
exp-postfix  ::= Ø
               | '[' exp ']' exp-postfix
               | '(' exp ')' exp-postfix 
term         ::= Name | '(' exp ')' 
a-or-fc-statement ::= term a-postfix
a-postfix         ::= '=' exp 
                    | ac-postfix
c-postfix         ::= Ø
                    | ac-postfix
ac-postfix        ::= '(' exp ')' c-postfix
                    | '[' exp ']' a-postfix
statement-list ::= Ø
                 | s1 statement-list
                 | s2 s2-postfix
                 | s3 s2-postfix
s2-postfix     ::= Ø
                 | s1 statement-list
                 | s2 s2-postfix
s1             ::= block | ';'
s2             ::= Name a-postfix
s3             ::= '(' exp ')' a-postfix