如何删除不正确的左递归

时间:2015-11-14 19:38:31

标签: parsing compiler-construction javacc

我需要帮助从这个语法中删除间接左递归:

A -> B (sB)*
     | dAd
     | z

B -> <id> 
     | sB 
     | A

所以你可以从A-> B-> A ...移动而不消耗任何字符。

我试图通过几种不同的方式修复它,但由于这一点(sB)*

而继续遇到问题

我不确定我做错了什么,或者语法是否错误。

3 个答案:

答案 0 :(得分:0)

有趣。我看不到这样做的机械方法。这是指定语言的方式,还是通过其他一些简化来最终得到它?无论如何,针对特定问题的解决方案是在左递归部分中“内联”B:

  

A - &gt; (&lt; id&gt; | sB | dAd | z)(sB)*
  B - &gt; &LT; ID&GT; | sB |甲

基本思想是用递归部分中的非递归项替换并将递归项移到最后。

答案 1 :(得分:0)

开始
A -> B (sB)* | dAd | z

B -> <id>  | sB  | A

替代

A -> (<id>  | sB  | A) (sB)* | dAd | z

定义

C -> (sB)*

替代

A -> (<id>  | sB  | A) C | dAd | z

因子

A -> <id> C  | sBC  | AC | dAd | z

定义

D -> <id> C  | sBC  | dAd | z

所以

A -> D  | AC 

删除左递归

A -> D (C)*

代替C和D

A ->  (<id> (sB)*  | sB(sB)*  | dAd | z) (sB)**

x** = x*

以来
A ->  (<id> (sB)* | sB(sB)* | dAd | z) (sB)*

x*x* = x*

以来
A ->  (<id>  | sB | dAd | z) (sB)*

B -> <id>  | sB  | A

与斯里尼瓦萨的结果相同。

在看到@ Rymoid的回答后添加了编辑。

此时左移递已被删除,所以我们完成了。但正如@Rymoid指出的那样,语法仍然含糊不清,因此不是LL(1)。下面我将尝试处理歧义,但不是找到LL(1)语法。

一个问题是,自A =>* sB以来,选择sB | A不明确且不需要。让我们从删除该选择开始。我们有

A ->  (<id>  | sB | dAd | z) (sB)*

B -> <id>  | A

同样A =>* <id>,因此选择<id> | A不明确且不需要。我们有

A ->  (<id>  | sB | dAd | z) (sB)*

B -> A

然后我们不再需要B

A ->  (<id>  | sA | dAd | z) (sA)*

剩下的问题是,由于s位于A的后续集合中,因此无法通过一个前瞻标记来判断是否留在(sA)*循环或退出。

原始问题没有要求LL(1)语法,但由于帖子被标记为[JavaCC],我们可能会认为所需的是与JavaCC一起使用的。这与LL(1)并不完全相同,尽管LL(1)意味着语法可以很好地与JavaCC一起使用。

我假设在A定义之外A的所有用途绝对不会被s所遵循。具体来说,我假设(仅)还有一个生产S -> A <EOF>,而S是非终结的开始。但真正重要的是,除了A当前定义中的循环之外,您永远不会有s后跟A

我们有

S -> A <EOF>
A ->  (<id>  | sA | dAd | z) (sA)*

当你有一个含糊不清的语法但想要消除歧义时,问自己的问题是:在模糊的情况下我想要哪个解析?两个答案是:&#34;尽可能长时间地保持循环。&#34;和&#34;尽快跳出循环。&#34; (其他答案是可能的,但不太可能。)

&#34;尽可能长时间地保持循环&#34;

这是JavaCC的默认值,因此无需更改语法。它可能会生成警告。可以在循环开始时用LOOKAHEAD( <s> )来抑制该警告。

&#34;尽快退出循环&#34;

制作A的两个版本。 A0永远不会跟sA1后面跟着s。 (事实上​​它后面跟着第一个s,所以不需要(sA)*部分。这个选择对应于尽快退出循环。)

S -> A0 <EOF>
A0 ->  (<id>  | sA0 | dA0d | z) [ s (A1s)* A0 ]
A1 ->  <id>  | sA1 | dA0d | z

我非常确定这是明确的,A0定义的语言与A相同。它不是LL(1),JavaCC会给出一个应该注意的警告。

为了使它适合JavaCC,我们可以在循环开始时添加LOOKAHEAD( A1 <s> )的语法预测。

答案 2 :(得分:0)

在开始之前,让我们为您的作品编号,以便我们可以参考:

1:  A -> B (s B)*
2:  A -> d A d
3:  A -> z
4:  B -> <id>
5:  B -> s B
6:  B -> A

由于您试图消除左递归,我只能假设您尝试应用LL解析。但是,这个语法含糊不清,因此它不能成为LL(1)语法。例如,短语zszsz可以(最左边)以多种方式从A派生:

A  ->+  B s B      (1)
   ->+  A s B      (6)
   ->+  z s B      (3)
   ->+  z s B s B  (1)
   ->+  z s z s z  (6, 3, 6, 3)

A  ->+  B s B      (1)
   ->+  A s B      (6)
   ->+  B s B s B  (1)
   ->+  A s B s B  (6)
   ->+  z s B s B  (3)
   ->+  z s z s z  (6, 3, 6, 3)

第一步是简化这种语法,这样每个产品只会在&#34;扩展&#34;侧。规则#1有一个Kleene星,所以让我们通过用非终端C取代它来摆脱它:

1:  A -> B C
2:  A -> d A d
3:  A -> z
4:  B -> <id> 
5:  B -> s B 
6:  B -> A
7:  C -> <empty>
8:  C -> s B C

现在,所有制作都很简单。

接下来,我们识别间接左递归(如果有的话),并将其转换为直接左递归。通过查看以非终端开头的所有产品,我们发现AB涉及间接左递归(通过规则#1和#6)。我们可以通过用规则#1中的B代替它可以产生的东西来打破这个循环;我们

替换规则#1
9:  A -> <id> C
10: A -> s B C
11: A -> A C

或者,我们可以通过替换#6中的作品#1,#2和#3来打破循环。但是我们这样做,结果语法没有间接的左递归。

然后我们在语法中消除直接左递归(如果有的话)。由于我们的替换,这发生在非终端A中:

2:  A -> d A d
3:  A -> z
...
9:  A -> <id> C
10: A -> s B C
11: A -> A C

我们引入另一个非终端D,并用

替换这些规则
12: A -> d A d D
13: A -> z D
14: A -> <id> C D
15: A -> s B C D
17: D -> <empty>
18: D -> A D

结果语法没有左递归:

4:  B -> <id> 
5:  B -> s B 
6:  B -> A
7:  C -> <empty>
8:  C -> s B C
12: A -> d A d D
13: A -> z D
14: A -> <id> C D
15: A -> s B C D
17: D -> <empty>
18: D -> A D

如开头所述,您无法根据此语法构造LL(1)解析表,因为来自zszsz的{​​{1}}的最左侧推导仍然不明确。