【问题标题】:BNF: input going to wrong nonterminalBNF:输入错误的非终结符
【发布时间】:2010-12-02 00:37:12
【问题描述】:

我正在为国际象棋代数符号开发 BNF,遇到了一个有趣的案例,输入到错误的非终端。

我的开始 BNF 规则如下(请注意,这故意不包括 castling 或 notes):

algebraic_notation : piece start_position capture end_position promotion

piecestart_positioncapturepromotion 可以为空,从而允许像 'd4' 这样的移动。问题是当输入这样的移动时,输入('d4') 被start_position 占用,从而导致错误b/c 没有更多输入用于end_position,不能为空。

显而易见的破解/解决方法是让end_position 为空,然后检查我们是否有任何输入并采取相应措施。

这确实有效,但我想知道是否有办法解决这个问题。如果导致整个表达式不匹配,输入是否可能不去第一个匹配的符号?

另一个问题是这是 BNF 的标准行为,还是我正在使用的 yaccer 的问题:PLY v 3.3。

尝试使用 flex/bison 并得到相同的结果。所以它似乎并不特定于 PLY。

以下是完整性的所有相关规则:

algebraic_notation : piece start_position capture end_position promotion

piece : KING
        | QUEEN
        | BISHOP
        | KNIGHT
        | ROOK
        | pawn

pawn : empty

start_position : FILE
                | NUMBER
                | FILE NUMBER
                | empty

end_position : FILE NUMBER
                | empty                 // this line is the hack/workaround

capture : CAPTURE
        | empty

promotion : EQUAL QUEEN
            | EQUAL ROOK
            | EQUAL KNIGHT
            | EQUAL BISHOP
            | empty

empty : 

【问题讨论】:

  • yacc 对于这个问题是不是有点矫枉过正?
  • @cam 也许。但是根据我的经验,手动解析字符串并不是那么清晰或可读。
  • 另外,即使 BNF 对于这个特定的应用程序来说太过分了,在更复杂的语法中仍然可能遇到这个问题。无论如何,我有一个解决方法/黑客;我只是想尽可能使用更好的解决方案。

标签: yacc bnf ply


【解决方案1】:

问题是您忽略了从解析器生成器获得的移位/减少冲突。虽然 yacc/bison(可能是 PLY)会为您解决错误,但该解决方案可能无法满足您的需求,并且可能导致解析器解析您尝试解析的语言之外的语言。

每当您从 LR 解析器生成器中获得 shift/reduce(或 reduce/reduce)冲突时,您确实需要了解冲突是什么(以及为什么会发生)才能知道是否可以忽略它或是否需要修理它。因此,让我们通过摆脱“hack”(这显然是错误的,而不是您想要解析的东西)以及无用的“empty”规则(只会混淆事物)来修复您的语法:

%token FILE NUMBER
%%
algebraic_notation : piece start_position capture end_position promotion
piece : 'K' | 'Q' | 'B' | 'N' | 'R' | /*pawn*/
start_position : FILE | NUMBER | FILE NUMBER | /*empty*/
end_position : FILE NUMBER
capture : 'x' | /*empty*/
promotion : '=' 'Q' | '=' 'R' | '=' 'N' | '=' 'B' | /*empty*/

现在,当您通过 'bison -v' 运行它时(总是使用 -v 来获取详细的输出文件——我不确定 PLY 的等价物是什么),您会收到有关 shift/reduce 冲突的消息,并且如果您查看.output 文件,您可以看到它是什么:

state 7

    1 algebraic_notation: piece . start_position capture end_position promotion

    FILE    shift, and go to state 9
    NUMBER  shift, and go to state 10

    FILE      [reduce using rule 11 (start_position)]
    $default  reduce using rule 11 (start_position)

    start_position  go to state 11

这告诉你,在看到piece 之后,当下一个标记是FILE 时,它不知道它是否应该转移(将FILE 视为start_position 的(一部分))或减少(给一个空的start_position)。那是因为它需要更多的前瞻性来查看是否有第二个位置可以用作end_position 来知道要做什么,所以简单地忽略冲突将导致解析器无法解析很多有效的东西(基本上,任何带有空start_positioncapture)。

解决涉及像这样的空产生式(或几乎任何涉及空产生式的冲突,实际上)的与前瞻相关的移位减少冲突的最佳方法是分解语法 - 摆脱空规则并重复任何使用和不使用非终结符的规则。就您而言,这为您提供了规则:

algebraic_notation : piece capture end_position promotion
algebraic_notation : piece start_position capture end_position promotion
start_position : FILE | NUMBER | FILE NUMBER 

(其他规则不变) 这样一来,您仍然存在换班冲突:

state 7

    1 algebraic_notation: piece . capture end_position promotion
    2                   | piece . start_position capture end_position promotion

    FILE    shift, and go to state 9
    NUMBER  shift, and go to state 10
    'x'     shift, and go to state 11

    FILE  [reduce using rule 14 (capture)]

    start_position  go to state 12
    capture         go to state 13

基本上,我们只是将冲突转移了一步,现在出现了空 capture 规则的问题。所以我们也将其分解:

algebraic_notation : piece end_position promotion
algebraic_notation : piece capture end_position promotion
algebraic_notation : piece start_position end_position promotion
algebraic_notation : piece start_position capture end_position promotion
capture : 'x'

现在 bison 不再报告冲突,因此我们可以有理由相信它会按照我们想要的方式解析。您可以通过去掉capture 规则并在algebraic_notation 规则中使用文字'x' 来进一步简化它。我个人更喜欢这个,因为我认为避免不必要的间接更清楚:

%token FILE NUMBER
%%
algebraic_notation : piece end_position promotion
algebraic_notation : piece 'x' end_position promotion
algebraic_notation : piece start_position end_position promotion
algebraic_notation : piece start_position 'x' end_position promotion
piece : 'K' | 'Q' | 'B' | 'N' | 'R' | /*pawn*/
start_position : FILE | NUMBER | FILE NUMBER
end_position : FILE NUMBER
promotion : '=' 'Q' | '=' 'R' | '=' 'N' | '=' 'B' | /*empty*/

【讨论】:

    【解决方案2】:

    我没有使用 PLY,并且没有看到您尝试过的完整 flex/bison 文件,我可能会选择一个非问题,但在我看来,您并没有让解析器知道没有更多的东西来了对于当前的algebraic_notation 规则。您没有说您如何知道输入 'd4' 与 start_position 匹配,但如果解析器知道它具有规则的所有标记,并且唯一的非空标记是 end_position,则它必须将 'd4' 匹配到那个。

    如果引入一个标记行尾的标记,例如 EOL。所以你的第一条规则变成:

    algebraic_notation : piece start_position capture end_position promotion EOL
    

    并且解析器现在看到令牌“d4”后跟 EOL——这会改变行为吗?

    【讨论】:

    • 没有。发生的情况是 'd4' 转到 start_position,然后我们必须将 EOL 匹配到 end_positionEOL。它并没有改变解析器似乎没有查看整个规则的事实,而是只是将输入与它可以匹配的规则中的第一件事相匹配,而不管它是否导致它不匹配整个规则。
    【解决方案3】:

    如果您将start_position capture end_position 包装到一个中间块中,然后将FILE NUMBER 从 start_pos 中删除,会发生什么情况:

    middle: start_pos capture end_pos
          | end_pos capture end_pos
          | capture end_pos
    
    start_pos : FILE
          | NUMBER
          | empty
    
    end_pos : FILE NUMBER
    
    capture : CAPTURE
          | empty
    

    【讨论】:

    • 同样的结果。除非有办法指定middle 的第一行优先于第二行,否则'd4' 只会转到第二行的第一行end_pos,从而导致第二行@987654326 没有任何输入@.
    【解决方案4】:

    这个问题很好地说明了计算机科学理论问题,即从语法中删除 epsilon(或空)产生式。国际象棋符号的歧义问题可以通过简化语法以删除空产生式来解决(对于 yacc 或 PLY)。 SO/SE 和其他站点上都有很多关于此的材料。我为感兴趣的读者附上了参考书目。

    通过对规则进行无意识转换以删除盲/空/epsilon 产生式,我们得到以下 CFG:

    algebraic_notation : piece start_position capture end_position promotion
                       | piece start_position capture end_position 
                       | piece start_position capture promotion
                       | piece start_position end_position promotion
                       | piece capture end_position promotion
                       | piece start_position capture
                       | piece start_position end_position
                       | piece capture end_position
                       | piece start_position promotion
                       | piece capture promotion
                       | piece end_position promotion
                       | piece promotion
                       | piece end_position 
                       | piece capture 
                       | piece start_position 
                       | piece 
                       | start_position capture end_position promotion
                       | start_position capture end_position
                       | start_position capture promotion
                       | start_position end_position promotion
                       | capture end_position promotion
                       | start_position capture
                       | start_position end_position
                       | capture end_position
                       | end_position promotion
                       | start_position 
                       | capture 
                       | end_position
                       | promotion
    piece : KING
            | QUEEN
            | BISHOP
            | KNIGHT
            | ROOK
    
    start_position : FILE
                    | NUMBER
                    | FILE NUMBER
    
    end_position : FILE NUMBER
    
    capture : CAPTURE
    
    promotion : EQUAL QUEEN
                | EQUAL ROOK
                | EQUAL KNIGHT
                | EQUAL BISHOP
    

    (这可以通过删除那些不能出现在国际象棋符号中的组合来简化,但这是给读者的练习)。

    参考书目

    这方面最好的书可能是:

    或者直接看 Jeff Ullman 的幻灯片:

    或者一堆关于 SO/SE 的相关问题:

    【讨论】:

    • 消除所有 epsilon 规则通常是矫枉过正。让您的解析器生成器指导您通常要好得多,并且只需消除那些导致冲突的 epsilon 规则...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-11
    • 1970-01-01
    • 2021-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多