我重新提出了我之前提出的一个问题。目的是了解优先级如何在解析中起作用。
我想解析一句话a(3).value = 100
。 <{1}}如下所示,在阅读parser.mly
后停止,并返回错误。
但是,如果我将专用于.
的部分(由argument_list
和begin
加入)移动到文件末尾(因此它位于end
之后),解析效果很好。
在解析语句的情况下,它被缩减为l_expression
let_statement
; data_manipulation_statement
缩减为a(3).value
; member_access_expression
缩减为a(3)
;并且index_expression
缩减为3
。
在无法解析语句的情况下,似乎它试图将语句的开头减少到argument_list
的{{1}} ...它以错误结束。
我一直认为,无论优先级是什么,解析总是拒绝不能通过成功结束的减少。但似乎它尝试失败了,并拒绝尝试其他可能性......
有人可以帮忙解释一下吗?
call_statement
答案 0 :(得分:3)
关于解析如何进行没有绝对的规则。这取决于解析器。可以说,一个正确的解析器应该“总是拒绝不能通过成功结束的减少”,但是你通常不能使用线性时间从左到右的解析器。 GLR解析器(野牛可以生成)可以做到这一点,可能最多为立方时间,具体取决于语法。回溯解析器 - 例如大多数解析组合库 - 可以做到这一点,但估计算法的复杂性并不容易。但是我认为你正在使用的* LR(1)解析器,基于你的标签,只能解析* LR(1)语法,你的语法不是其中之一。
LR(1)解析器在输入上从左到右( L ),一次一个令牌。在阅读每个令牌后,它“减少”(即匹配一个产品)所有产品,这些产品以输入的最右边标记( R )结束。为此,它使用尽可能多的信息来保存解析的进度(“解析堆栈”)和下一个(1)令牌(“前瞻”)。它们使用有限状态下推自动机,并且有几种构造这些自动机的方法,它们为理想的PDA提供不同的近似值,但是对于无关紧要的语法而言。我相信ocamlyacc生成LALR(1)解析器,就像原来的yacc一样,但你的语法甚至不是LR(1),所以它肯定不能被简化的LR(1)解析器解析。
在上面的描述中,我没有使用“优先级”一词,因为它不适用于LR解析。有一些优先级解析器 - 它通常不如LR解析器强大,但也更容易构造 - 但它们不再那么常见,除了手写解析器的形式。 (我不确定是否有优先级语法解析器生成器;一旦发现LALR(1)解析,它就会迅速接管解析器生成器市场,因为它比优先级或LL(1)解析更强大。)然而,在yacc
的早期阶段,为了应对含糊不清的语法,“优先”被添加到call_statement: simple_name_expression argument_list
let_statement: l_expression EQUAL expression
l_expression: index_expression
l_expression: simple_name_expression
index_expression: l_expression LPAREN argument_list RPAREN
;或者,通常是语法,这些语法本来可以毫不含糊地写出来,但是它们的模糊形式更为紧凑。
当LR解析器决定做什么时,可能存在多个替代方案。这可能是因为存在多个可能的减少(“减少 - 减少”冲突),或者因为不清楚可用的减少是否正确(“减少 - 减少”冲突)。冲突的产生是因为语法不明确,或者因为它不能仅使用指定的超前进行LR解析。
在任何一种情况下,迂腐的LR解析器生成器都会报告失败。但是一个实用的解析器生成器可能会试图找出哪个替代方案是所需的,并相应地进行。这样做的一个简单方法是根据一些程序员提供的声明(“优先声明”)或基于某种启发式(“更喜欢语法文件中的第一个内容”)给予另一个(s)一个替代优先级。 )。这种启发式方法很危险,因为它们可以导致解析器实际上不解析您希望解析的语言;恕我直言,没有解析器生成器应该应用启发式,而不至少警告你它已经这样做了。
现在,实际问题:为什么你的语法不是LR(1)。
让我们从以下摘录开始。
a (
现在考虑输入:a
。也就是说,我们刚刚读取了令牌(
,前瞻令牌是a
。 simple_name_expression
必须缩减为·
(通过多个单元制作,但细节无关紧要)。现在,此时,解析器状态将包括:(call_statement: simple_name_expression · argument_list
index_expression: l_expression · LPAREN argument_list RPAREN
l_expression: simple_name_expression ·
表示产品中的当前“点”:
simple_name_expression
也就是说,我们可以保留argument_list
,然后继续收集simple_name_expression
,或者我们可以将l_expression
缩减为index_expression
,以便继续收集RPAREN
的其余部分。它是哪一个?
不幸的是,至少在我们达到匹配的l_expression
之前,我们无法知道答案。即便如此,我们可能也不知道答案,因为下一个标记可能会延伸.
(例如LPAREN
或另一个call_statement
),在这种情况下我们需要继续扫描。没有人知道什么时候我们会找到答案,所以没有有限的前瞻就足够了,这个语法 - 虽然可能是明确的 - 对于任何k都不是LR(k)。 (并且优先启发法也不起作用。)
顺便说一句,我不清楚argument_list
的语法是你想要的。 expression
可以以开头(,因为它必须以expression
开头,print a(3), value
可以括号括起来,但它不必以括号。所以以下是一个合法的call_statement:
{{1}}
如果这就是你想要的,那很好。