回溯如何影响解析器识别的语言?

9

这不是学校相关的问题,但在“编译原理(Compilers: Principles, Techniques, and Tools)”中的一个练习中提到:

文法如下:

S ::= aSa | aa

生成所有偶数个a的字符串,除了空字符串。

a)构造一个具有回溯功能的递归下降解析器来处理该文法,尝试使用替代的aSa而不是aa。表明对于2、4或8个a的情况,S的程序成功了,但在6个a的情况下失败了。 b)你的解析器识别哪种语言?

我被难住了。如果4个a被认为是S,并且在S之间有两个a被认出,则应该认出6个a。我尝试在C中实现解析器,但它也将所有偶数个a识别出来,包括6个a。这个练习想要表达什么意思?

/* A C implementation of Exercise 4.13 in the Dragon Book */

/* The grammar:

   S ::= aSa | aa

*/

/* Construct a recursive-descent parser with backtracking for this grammar 
   that tries the alternative aSa before aa. Show that the procedure for S 
   succeeds on 2, 4, or 8 a's, but fails on 6 a's. 
*/

#include <string.h>
#include <stdio.h>

int S(const char *str, int start, int end);
int aSa(const char *str, int start, int end);
int aa(const char *str, int start, int end);

/* returns 1 if a match, 0 otherwise */
int S(const char *str, int start, int end)
{
  if(aSa(str, start, end))
    return 1;
  else
    if(aa(str, start, end))
      return 1;
  return 0;
}

/* returns 1 if a match, 0 otherwise */
int aSa(const char *str, int start, int end)
{
  int len = end - start;
  if (len < 3)
    return 0;
  if(str[0] != 'a')
    return 0;
  if (!S(str, start+1, end-1))
    return 0;
  if(str[len-1] != 'a')
    return 0;
  return 1;
}

/* returns 1 if a match, 0 otherwise */
int aa(const char *str, int start, int end)
{
  int len = end - start;
  if(len != 2)
    return 0;
  if(str[0] == str[1] && str[0] == 'a')
    return 1;
  return 0;
}

int main()
{
  char str[20];
  printf("Enter a string: \n");
  scanf("%s", str);
  int match = S(str, 0, strlen(str));
  if(match)
    printf("The string %s matches\n", str);
  else
    printf("The string %s does not match\n", str);
  return 0;
}

能否被3整除是否与解析器的期望有关?这只是一个想法。 - usumoio
我不熟悉所有的术语,但我怀疑将 end-1 传递给 S 对于“递归下降解析器”来说是作弊。 - aschepler
@aschepler:事实上,这就是问题的关键。示例代码不是回溯解析器! :-) - torek
aSa 中,你检查的是 (str[0] != 'a'),但是应该是 (str[start] != 'a'),对吗? - hkBst
4个回答

6
问题不在于这是一个回溯或递归下降解析器,而在于所描述的实现没有正确考虑递归下降解析的外部上下文。这类似于强LL(SLL)解析器和LL解析器之间的区别。
表现出奇怪行为的最短输入是aaaaaa。
1. 我们从规则S开始,匹配第一个a。 2. 我们调用S。 - 我们匹配第二个a。 - 我们调用S。我将省略具体步骤,但关键是这次调用的S匹配了aaaa,即第3个a到输入结束。(见下面的注释。) - 我们尝试匹配a,但由于已经到达输入结尾,我们返回并仅匹配第二个到第三个aa。 3. 我们匹配第四个a。
有关匹配aaaa的内部调用S的其他说明:如果我们知道要在第3步保留一个a作为输入的结尾,则调用S可以匹配aa而不是aaaa,从而成功解析完整输入aaaaaa。ANTLR 4提供了这种“完整上下文”解析能力,在递归下降解析器中,并且是第一个能够正确匹配S的嵌套调用中的aa而不是aaaa的递归下降LL解析器。
SLL解析器对于这个文法匹配a^2^k。正确的LL解析器(如ANTLR 4)对于这个文法匹配a^2k。

很好的解释,但称其为递归下降是否公平?递归下降通常用于描述“天真”的实现吗? - andrew cooke
如果你看一下ANTLR 4生成的代码,它几乎是最明显的递归下降。它与回溯或其他实现的区别在于决策点上确定要扩展哪个非终端符号的方法的实现。 - Sam Harwell

3
即使使用回溯(backtracking)技术,需要能够倒带输入流(input stream),递归下降解析器仍然不允许预测输入结束的位置,也不允许移除输入流的开头和结尾的符号。
从左到右解析器(left-to-right parser)必须能够处理仅具有一种方法的输入流:
get() : consume and read one symbol, or return an EOF symbol.

回溯版本需要一个带有两个附加方法的流:
posn = tell()  : return an opaque value which can be used in seek()
seek(posn)     : reposition the stream to a previous position returned by tell()

好的,我已经修改了我的程序,使用了那些函数,现在它表现得很正常。谢谢。 - Daniel Dilger

2

我不想为了好玩而用C语言编写这个,但是这里有一个使用Python编写的解析器,我尽力将其简单化(即使您不知道这种语言,我希望它看起来像伪代码那样清晰明了):

class Backtrack(Exception): pass

def asa(input):
    if input[0:1] == 'a':
        parsed, remaining = s(input[1:])
        if remaining[0:1] == 'a':
            return 'a' + parsed + 'a', remaining[1:]
    raise Backtrack

def aa(input):
    if input[0:2] == 'aa':
        return 'aa', input[2:]
    raise Backtrack

def s(input):
    try:
        return asa(input)
    except Backtrack:
        return aa(input)

for i in range(17):
    print(i, ': ', end='')
    try:
        print(s('a' * i))
    except Backtrack:
        print('failed')

并将结果表示为长度: (已解析, 剩余):

0 : failed
1 : failed
2 : ('aa', '')
3 : ('aa', 'a')
4 : ('aaaa', '')
5 : ('aa', 'aaa')
6 : ('aaaa', 'aa')
7 : ('aaaaaa', 'a')
8 : ('aaaaaaaa', '')
9 : ('aa', 'aaaaaaa')
10 : ('aaaa', 'aaaaaa')
11 : ('aaaaaa', 'aaaaa')
12 : ('aaaaaaaa', 'aaaa')
13 : ('aaaaaaaaaa', 'aaa')
14 : ('aaaaaaaaaaaa', 'aa')
15 : ('aaaaaaaaaaaaaa', 'a')
16 : ('aaaaaaaaaaaaaaaa', '')

我猜这会帮助你理解。简单来说,递归下降是一种非常具体、有限的东西,它并不是一个完整的搜索。

(这是个很好的问题,它提出了一个重要的观点。好书。)


1
[aa]的分析过程 输入图像描述 [aaaa]的分析过程 输入图像描述 [aaaaaa]的分析过程 输入图像描述 [aaaaaaaa]的分析过程 输入图像描述 递归下降解析器只在出现错误时进行回溯。 它无法处理成功是“临时”的情况。

如果您将图表放在链接后面而不是内联,那么您的答案会更好。不幸的是,我的编辑队列已满... - hkBst
你是指这个,对吗? - Tzu7

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接