Python 解决开关块中默认标签的移位/减少冲突

Python 解决开关块中默认标签的移位/减少冲突,python,ply,lalr,unrealscript,Python,Ply,Lalr,Unrealscript,我正在使用PLY为Unrealscript编写一个解析器,我(希望)遇到了我所设置的解析规则中最后的一个歧义 Unrealscript有一个关键字,default,根据上下文的不同使用不同的关键字。在常规语句行中,您可以像这样使用default: default.SomeValue = 3; // sets the "default" class property to 3 当然,开关语句也有默认案例标签: switch (i) { case 0: break;

我正在使用PLY为Unrealscript编写一个解析器,我(希望)遇到了我所设置的解析规则中最后的一个歧义

Unrealscript有一个关键字,
default
,根据上下文的不同使用不同的关键字。在常规语句行中,您可以像这样使用
default

default.SomeValue = 3;  // sets the "default" class property to 3
当然,
开关
语句也有
默认
案例标签:

switch (i) {
    case 0:
        break;
    default:
        break;
}
当它在它认为是
case
的语句块中遇到
default
标签时,解析过程中存在歧义。以下是遇到解析错误的示例文件:

输入 解析规则 以下是相互冲突的规则:

所有规则都可以完整地看到

默认值
开关

对于如何解决此冲突的任何帮助或指导,我们将不胜感激!提前感谢您。

您这里有一个简单的移位/减少冲突(将标记
默认值
作为前瞻)作为移位解决

让我们把这一切简化为一个小得多的例子。以下是语法,部分基于OP中所指的Github存储库中的语法(但旨在自包含):

这里的关键是
语句
可能以令牌
默认值
开头,而
案例
也可能以令牌
默认值
开头。现在,假设我们在解析中达到了以下点:

switch ( <expression> ) { <cases> case <expression> : <statements>
项目2的先行设置为
[r默认,默认,ID]

现在假设下一个标记是默认标记。如果缺省值后跟=,我们可以查看语句的开头。或者,如果默认值后跟:。但我们看不到未来的两个标志,只有一个;下一个标记是default,这就是我们所知道的

但我们需要做出决定:

  • 如果默认值是语句的开头,我们可以将其移位(第5项)。然后,当我们看到=,我们将把默认值减少为
    lvalue
    ,并继续解析
    assign

  • 如果默认值是案例的开头,我们需要将
    案例表达式冒号语句
    减少为
    案例
    (第2项)。然后,我们将把
    cases-case
    减少到
    cases-case
    ,然后再最终转换默认值。然后,我们将移动:并继续使用
    默认冒号语句

与大多数LR解析器生成器一样,PLY通过移位来解决移位/减少冲突,因此它总是采用上面两个选项中的第一个。如果随后看到:而不是=,它将报告语法错误

所以我们所看到的只是LR(2)语法的另一个例子。LR(2)语法总是可以重写为LR(1)语法,但重写的语法通常是丑陋和臃肿的。这里有一个可能的解决方案,它可能没有大多数方案那么丑陋

使用EBNF运算符
|
*
+
(交替、可选重复和重复)的开关主体是:

或者,为了让它不那么麻烦:

case-header -> ("case" expression | "default") ":"
switch-body -> (case-header statement*)+
从接受字符串的角度来看,这与

switch-body -> case-header (case-header | statement)*
换言之,是一系列事物,它们要么是
案例标题
s,要么是
语句
s,其中第一个是
案例标题

这种编写规则的方式不会生成正确的解析树;它将switch语句的结构简化为语句和大小写标签的组合。但它确实能识别完全相同的语言

另一方面,它的优点是不强制解析器决定案例原因何时终止。(该语法不再包含大小写子句)因此它是一个简单的LR(1)语法:

现在,我们可以证明结果解析树实际上是准确的。Unrealscript与C共享关于
switch
语句的相同设计决策,其中
case
子句实际上并不定义任何真正意义上的块。它只是一个可以跳转到的标签,以及一个条件跳转到下一个标签

但实际上,在我们进行操作时修复解析树并不特别复杂,因为对
switch\u body
的每次缩减都清楚地表明了我们要添加的内容。如果我们添加一个case头,我们可以在case子句的累加列表中附加一个新列表;如果它是一个语句,我们将该语句附加到最后一个case子句的末尾

因此,我们可以将上述规则大致写为:

def p_switch_body_1(p):
    ''' switch_body  : case_header '''
    p[0] = [p[1]]

def p_switch_body_2(p):
    ''' switch_body  : switch_body statement '''
    # Append the statement to the list which is the last element of
    # the tuple which is the last element of the list which is the
    # semantic value of symbol 1, the switch_body.
    p[1][-1][-1].append(p[2])
    p[0] = p[1]

def p_switch_body_3(p):
    ''' switch_body  : switch_body case_header '''
    # Add the new case header (symbol 2), whose statement list
    # is initially empty, to the list of switch clauses.
    p[1].append(p[2])
    p[0] = p[1]

def p_case_header_1(p):
    ''' case_header  : CASE expr COLON '''
    p[0] = ('switch_case', p[2], [])

def p_case_header_2(p):
    ''' case_header  : DEFAULT COLON '''
    p[0] = ('default_case', [])

您是否尝试将
p_案例
规则移动到
p_默认
规则之前?它可能不会消除歧义,但它会导致解析器执行正确的操作。嗯,没有这样的运气。同样的结果。@Bakuriu:类似yacc的解析器生成器中的结果顺序是不相关的。
switch ( <expression> ) { <cases> case <expression> : <statements>
1. statements: statements · statement
2. case      : CASE expression COLON statements ·
3. statement : · assign SEMICOLON
4. assign    : · lvalue EQUALS expression
5. lvalue    : · DEFAULT
switch-body -> (("case" expression | "default") ":" statement*)+
case-header -> ("case" expression | "default") ":"
switch-body -> (case-header statement*)+
switch-body -> case-header (case-header | statement)*
switch       : SWITCH LPAREN expression RPAREN LCURLY switch_body RCURLY
switch_body  : case_header
             | switch_body statement
             | switch_body case_header
case_header  : CASE expr COLON
             | DEFAULT COLON
def p_switch_body_1(p):
    ''' switch_body  : case_header '''
    p[0] = [p[1]]

def p_switch_body_2(p):
    ''' switch_body  : switch_body statement '''
    # Append the statement to the list which is the last element of
    # the tuple which is the last element of the list which is the
    # semantic value of symbol 1, the switch_body.
    p[1][-1][-1].append(p[2])
    p[0] = p[1]

def p_switch_body_3(p):
    ''' switch_body  : switch_body case_header '''
    # Add the new case header (symbol 2), whose statement list
    # is initially empty, to the list of switch clauses.
    p[1].append(p[2])
    p[0] = p[1]

def p_case_header_1(p):
    ''' case_header  : CASE expr COLON '''
    p[0] = ('switch_case', p[2], [])

def p_case_header_2(p):
    ''' case_header  : DEFAULT COLON '''
    p[0] = ('default_case', [])