解决转移/减少冲突

时间:2017-03-31 16:29:06

标签: parsing yacc bnf ebnf shift-reduce-conflict

我使用PLY来解析this语法。我为链接规范中使用的EBNF实现了元数据,但PLY报告了多个移位/减少冲突。

语法:

Rule 0     S' -> grammar
Rule 1     grammar -> prod_list
Rule 2     grammar -> empty
Rule 3     prod_list -> prod
Rule 4     prod_list -> prod prod_list
Rule 5     prod -> id : : = rule_list
Rule 6     rule_list -> rule
Rule 7     rule_list -> rule rule_list
Rule 8     rule -> rule_simple
Rule 9     rule -> rule_group
Rule 10    rule -> rule_opt
Rule 11    rule -> rule_rep0
Rule 12    rule -> rule_rep1
Rule 13    rule -> rule_alt
Rule 14    rule -> rule_except
Rule 15    rule_simple -> terminal
Rule 16    rule_simple -> id
Rule 17    rule_simple -> char_range
Rule 18    rule_group -> ( rule_list )
Rule 19    rule_opt -> rule_simple ?
Rule 20    rule_opt -> rule_group ?
Rule 21    rule_rep0 -> rule_simple *
Rule 22    rule_rep0 -> rule_group *
Rule 23    rule_rep1 -> rule_simple +
Rule 24    rule_rep1 -> rule_group +
Rule 25    rule_alt -> rule | rule
Rule 26    rule_except -> rule - rule_simple
Rule 27    rule_except -> rule - rule_group
Rule 28    terminal -> SQ string_no_sq SQ
Rule 29    terminal -> DQ string_no_dq DQ
Rule 30    string_no_sq -> LETTER string_no_sq
Rule 31    string_no_sq -> DIGIT string_no_sq
Rule 32    string_no_sq -> SYMBOL string_no_sq
Rule 33    string_no_sq -> DQ string_no_sq
Rule 34    string_no_sq -> + string_no_sq
Rule 35    string_no_sq -> * string_no_sq
Rule 36    string_no_sq -> ( string_no_sq
Rule 37    string_no_sq -> ) string_no_sq
Rule 38    string_no_sq -> ? string_no_sq
Rule 39    string_no_sq -> | string_no_sq
Rule 40    string_no_sq -> [ string_no_sq
Rule 41    string_no_sq -> ] string_no_sq
Rule 42    string_no_sq -> - string_no_sq
Rule 43    string_no_sq -> : string_no_sq
Rule 44    string_no_sq -> = string_no_sq
Rule 45    string_no_sq -> empty
Rule 46    string_no_dq -> LETTER string_no_dq
Rule 47    string_no_dq -> DIGIT string_no_dq
Rule 48    string_no_dq -> SYMBOL string_no_dq
Rule 49    string_no_dq -> SQ string_no_dq
Rule 50    string_no_dq -> + string_no_dq
Rule 51    string_no_dq -> * string_no_dq
Rule 52    string_no_dq -> ( string_no_dq
Rule 53    string_no_dq -> ) string_no_dq
Rule 54    string_no_dq -> ? string_no_dq
Rule 55    string_no_dq -> | string_no_dq
Rule 56    string_no_dq -> [ string_no_dq
Rule 57    string_no_dq -> ] string_no_dq
Rule 58    string_no_dq -> - string_no_dq
Rule 59    string_no_dq -> : string_no_dq
Rule 60    string_no_dq -> = string_no_dq
Rule 61    string_no_dq -> empty
Rule 62    id -> LETTER LETTER id
Rule 63    id -> LETTER DIGIT id
Rule 64    id -> LETTER
Rule 65    id -> DIGIT
Rule 66    rest_of_id -> LETTER rest_of_id
Rule 67    rest_of_id -> DIGIT rest_of_id
Rule 68    rest_of_id -> empty
Rule 69    char_range -> [ UNI_CH - UNI_CH ]
Rule 70    empty -> <empty>

冲突:

id  : LETTER LETTER id
            | LETTER DIGIT id
            | LETTER
            | DIGIT

state 4

    (62) id -> LETTER . LETTER id
    (63) id -> LETTER . DIGIT id
    (64) id -> LETTER .

  ! shift/reduce conflict for LETTER resolved as shift
  ! shift/reduce conflict for DIGIT resolved as shift
    LETTER          shift and go to state 10
    DIGIT           shift and go to state 9
    |               reduce using rule 64 (id -> LETTER .)
    -               reduce using rule 64 (id -> LETTER .)
    (               reduce using rule 64 (id -> LETTER .)
    SQ              reduce using rule 64 (id -> LETTER .)
    DQ              reduce using rule 64 (id -> LETTER .)
    [               reduce using rule 64 (id -> LETTER .)
    $end            reduce using rule 64 (id -> LETTER .)
    )               reduce using rule 64 (id -> LETTER .)
    :               reduce using rule 64 (id -> LETTER .)
    ?               reduce using rule 64 (id -> LETTER .)
    *               reduce using rule 64 (id -> LETTER .)
    +               reduce using rule 64 (id -> LETTER .)

  ! LETTER          [ reduce using rule 64 (id -> LETTER .) ]
  ! DIGIT           [ reduce using rule 64 (id -> LETTER .) ]

id规则应该保证制作&#39; ids以字母开头。

下一次冲突:

    rule_alt        : rule '|' rule

state 113

    (25) rule_alt -> rule | rule .
    (25) rule_alt -> rule . | rule
    (26) rule_except -> rule . - rule_simple
    (27) rule_except -> rule . - rule_group

  ! shift/reduce conflict for | resolved as shift
  ! shift/reduce conflict for - resolved as shift
    (               reduce using rule 25 (rule_alt -> rule | rule .)
    SQ              reduce using rule 25 (rule_alt -> rule | rule .)
    DQ              reduce using rule 25 (rule_alt -> rule | rule .)
    LETTER          reduce using rule 25 (rule_alt -> rule | rule .)
    DIGIT           reduce using rule 25 (rule_alt -> rule | rule .)
    [               reduce using rule 25 (rule_alt -> rule | rule .)
    )               reduce using rule 25 (rule_alt -> rule | rule .)
    $end            reduce using rule 25 (rule_alt -> rule | rule .)
    |               shift and go to state 76
    -               shift and go to state 74

  ! |               [ reduce using rule 25 (rule_alt -> rule | rule .) ]
  ! -               [ reduce using rule 25 (rule_alt -> rule | rule .) ]

连接到一个熟悉的人:

rule_except     : rule '-' rule_simple
                | rule '-' rule_group

如何解决这些问题?

2 个答案:

答案 0 :(得分:2)

首先,你显然是盲目地翻译了语法。您需要标记输入流。

通常情况下,像id这样的东西会被词法分析器识别出来,而不是作为语法的一部分进行解析

id  : LETTER LETTER id
        | LETTER DIGIT id
        | LETTER
        | DIGIT

看起来 terminal 下的所有内容都不应该是语法的一部分。

其次,在语法中使用正确的递归。虽然LALR适用于左右递归,但是左递归会得到较小的表。

假设您有输入字符串AA

如果您坚持要解析标识符,那么您需要更像

的内容
id : id LETTER
   | id DIGIT
   | LETTER

最后,Shift-Reduce冲突不一定是基础的。它们经常出现在数值表达式中,由运算符先例解析。

减少 - 减少冲突总是不好。

答案 1 :(得分:2)

你真的应该认真考虑使用通常的扫描仪/解析器架构。否则,你将不得不找到一种处理空格的方法。

实际上,你似乎完全忽略了空白。这意味着解析器无法看到three consecutive identifiers之间的空白。它会看到它们作为asoupofundifferentiatedletters一起运行,它无法知道原始意图是什么。这使得你的语法非常模糊,因为在语法中,两个标识符可以相互跟随,假设某些东西会导致它们彼此区分。模棱两可的语法总会导致LR冲突。

让词法分析器识别的标识符(和其他多字符标记)更容易 。否则,您将不得不重写语法以识别允许空格的所有位置(例如(identifer1|identifier2)内的标点符号)或必需的(例如two identifiers)。

使用正则表达式识别扫描仪中的标识符还将消除语法和标识符的其他问题:

id -> LETTER LETTER id
id -> LETTER DIGIT id
id -> LETTER

这些规则要求id为奇数个字符,其中数字仅出现在偶数位置。因此a1b将是id,而不是ab1aba1。我确定这不是你的意思。

您似乎试图避免左递归。相反,你应该接受左递归。自下而上的解析器,如PLY,喜欢左递归。 (它们处理右递归,但代价是过多的解析器堆栈使用。)所以你真正想要的是:

id: LETTER | id LETTER | id DIGIT

语法中还有其他地方需要进行类似的更改。

另一个冲突是由您对运算符优先级的非正统处理引起的,这也可能是您尝试避免左递归的结果。与代数运算符一样,EBNF运算符可以使用简单的优先级方案进行解析。但是,由于“不可见”连接运算符,优先级声明(%left和朋友)的使用会很复杂。通常,您会发现在标准expr/factor/term代数语法中使用显式优先级更容易。在你的情况下,等价物将是这样的:

item: id
    | terminal
    | '(' rule ')'
term: item
    | item '*'
    | item '+'
    | item '?'
seq : term
    | seq term
alt : seq
    | alt '|' seq
except: term '-' term
rule: alt
    | except

上述except的处理对应于缺少有关-运算符优先级的信息。通过有效地禁止任何-|运算符组合而没有明确括号来表达这一点。

您还会在此处发现转移/减少冲突:

# The following will create a problem
prod: id "::=" rule
prod_list
    : prod
    | prod_list prod

(注意:我用左递归写的这个事实不会产生问题。)

这不是模棱两可的,但它不是从一个前瞻标记可从左到右解析的。它需要两个令牌,因为您无法知道id是否是当前正在解析的序列的一部分,或者是新生产的开始,直到您在id之后看到令牌:if它是::=,然后id是新产品的开始,不应该转移到当前规则。对该问题的通常解决方案是词法分析器中的黑客攻击:词法分析器由一个函数包装,该函数保留一个额外的前瞻标记,以便它可以将id ::=作为definition类型的单个标记发出。在其他SO问题中,有许多针对各种LR解析器的黑客攻击的例子。

说完所有这些之后,我真的不明白为什么要为EBNF构建解析器以解析XML。从EBNF构建一个有效的解析器基本上就是PLY所做的,除了它没有实现“E”部分,所以你必须重写使用?*,{{1}的规则。 }和+运算符。这可以自动处理,虽然-运算符通常是非平凡的,但它并不简单。恕我直言,将少数 E BNF规则重写为BNF然后只使用PLY会更容易。但如果你正在寻找挑战,那就去吧。