Parsing 解决轮班/减少冲突

Parsing 解决轮班/减少冲突,parsing,yacc,bnf,ebnf,shift-reduce-conflict,Parsing,Yacc,Bnf,Ebnf,Shift Reduce Conflict,我用它来解析语法。我为链接规范中使用的EBNF实现了元语法,但PLY报告了多个shift/reduce冲突 语法: 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

我用它来解析语法。我为链接规范中使用的EBNF实现了元语法,但PLY报告了多个shift/reduce冲突

语法:

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
规则应该保证产品的id以字母开头

下一个冲突:

    rule_alt        : rule '|' rule

连接到一个微笑的人:

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

我该如何解决这些问题呢?

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

通常,像id这样的东西将是词汇分析器识别的终端,而不是作为语法的一部分进行解析

id  : LETTER LETTER id
        | LETTER DIGIT id
        | LETTER
        | DIGIT
看起来终端下的所有内容都不应该是语法的一部分

第二,在语法中使用正确的递归。虽然LALR同时使用左递归和右递归,但使用左递归可以得到更小的表

假设您有输入字符串AA

如果你坚持解析标识符,你会想要更像

id : id LETTER
   | id DIGIT
   | LETTER
最后,移位-减少冲突不一定基于。它们经常出现在数值表达式中,由运算符先行解析


减少冲突总是不好的。

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

通常,像id这样的东西将是词汇分析器识别的终端,而不是作为语法的一部分进行解析

id  : LETTER LETTER id
        | LETTER DIGIT id
        | LETTER
        | DIGIT
看起来终端下的所有内容都不应该是语法的一部分

第二,在语法中使用正确的递归。虽然LALR同时使用左递归和右递归,但使用左递归可以得到更小的表

假设您有输入字符串AA

如果你坚持解析标识符,你会想要更像

id : id LETTER
   | id DIGIT
   | LETTER
最后,移位-减少冲突不一定基于。它们经常出现在数值表达式中,由运算符先行解析


减少冲突总是不好的。

您确实应该认真考虑使用常用的扫描程序/解析器体系结构。否则,您必须找到处理空白的方法

实际上,您似乎完全忽略了空格。这意味着解析器无法看到三个连续标识符之间的空格。它将看到它们作为一组无差别的字母一起运行,而且它无法知道最初的意图是什么。这使得你的语法变得模棱两可,因为在语法中,两个标识符可以互相跟随,前提是有什么东西会使它们彼此区别开来。歧义语法总是导致LR冲突

让lexer识别标识符(和其他多字符标记)要容易得多。否则,您必须重写语法以标识所有允许空白的位置(例如
(identifer1 | identifer2)
)或必需的位置(例如
两个标识符

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

id -> LETTER LETTER id
id -> LETTER DIGIT id
id -> LETTER
这些规则要求
id
为奇数字符,其中数字仅出现在偶数位置。因此
a1b
将是
id
,而不是
ab1
ab
a1
。我相信你不是这个意思

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

id: LETTER | id LETTER | id DIGIT
语法中还有其他地方需要做类似的修改

另一个冲突是由运算符优先级的非常规处理引起的,这也可能是您试图避免左递归的结果。EBNF运算符可以像代数运算符一样,使用简单的优先级方案进行解析。但是,优先声明(
%left
和friends)的使用将因为“不可见”的串联运算符而变得复杂。通常,您会发现在标准代数语法中使用显式优先级更容易。在您的情况下,等价物如下所示:

item: id
    | terminal
    | '(' rule ')'
term: item
    | item '*'
    | item '+'
    | item '?'
seq : term
    | seq term
alt : seq
    | alt '|' seq
except: term '-' term
rule: alt
    | except
上述中除之外的
处理对应于缺少有关
-
运算符优先级的信息。这可以通过有效地禁止没有显式括号的
-
|
运算符的任何混合来表示

您还会发现,您在这里有一个转移/减少冲突:

# The following will create a problem
prod: id "::=" rule
prod_list
    : prod
    | prod_list prod
(注意:我用左递归编写的事实不会造成问题。)

这不是含糊不清的,但它不能用单个先行标记从左到右进行解析。它需要两个标记,因为在看到
id
后面的标记之前,您无法知道
id
是当前正在解析的序列的一部分,还是新产品的开始:如果是
:=
,然后,
id
是新产品的开始,不应转换为当前规则。解决这个问题的通常方法是对lexer进行破解:lexer由一个函数包装,该函数保留一个额外的lookahead标记,以便它可以将
id::=
作为
定义类型的单个标记发出。在其他SO问题中,有许多针对各种LR解析器的此类攻击示例


尽管如此,我真的不明白为什么要为EBNF构建解析器来解析XML。从EBNF构建一个工作解析器基本上就是PLY所做的,只是它没有实现“E”部分,所以您必须重写使用
*
+
-
运算符的规则。这是可以处理的
# The following will create a problem
prod: id "::=" rule
prod_list
    : prod
    | prod_list prod