解决交换机块中默认标签的移位/减少冲突

时间:2016-09-30 17:46:18

标签: python ply lalr unrealscript

我正在使用PLY为Unrealscript编写一个解析器,并且我(我希望)遇到了我设置的解析规则中最后一个含糊之处。

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

default.SomeValue = 3;  // sets the "default" class property to 3

当然,还有defaultswitch语句的案例标签:

switch (i) {
    case 0:
        break;
    default:
        break;
}

在它认为default的语句块中遇到case标签时,解析存在歧义。这是一个遇到解析错误的示例文件:

输入

class Example extends Object;

function Test() {
    switch (A) {
        case 0:
            default.SomeValue = 3;    // OK
        default:                      // ERROR: Expected "default.IDENTIFIER"
            break;
    }
}

解析规则

以下是冲突的规则:

可以完整地看到所有规则on GitHub

default

def p_default(p):
    'default : DEFAULT PERIOD identifier'
    p[0] = ('default', p[3])

switch

def p_switch_statement(p):
    'switch_statement : SWITCH LPAREN expression RPAREN LCURLY switch_cases RCURLY'
    p[0] = ('switch_statement', p[3], p[6])


def p_switch_case_1(p):
    'switch_case : CASE primary COLON statements_or_empty'
    p[0] = ('switch_case', p[2], p[4])


def p_switch_case_2(p):
    'switch_case : DEFAULT COLON statements_or_empty'
    p[0] = ('default_case', p[3])


def p_switch_cases_1(p):
    'switch_cases : switch_cases switch_case'
    p[0] = p[1] + [p[2]]


def p_switch_cases_2(p):
    'switch_cases : switch_case'
    p[0] = [p[1]]


def p_switch_cases_or_empty(p):
    '''switch_cases_or_empty : switch_cases
                             | empty'''
    p[0] = p[1]

非常感谢有关如何解决此冲突的任何帮助或指导!提前谢谢。

1 个答案:

答案 0 :(得分:1)

你在这里有一个简单的转移/减少冲突(将令牌default视为先行)被解决为转变。

让我们将这一切减少到一个更小的,如果不是最小的例子。这是语法,部分基于OP中指向的Github存储库中的一个(但是打算是自包含的):

statements: statements statement |
statement : assign SEMICOLON
          | switch
assign    : lvalue EQUALS expression
switch    : SWITCH LPAREN expression RPAREN LCURLY cases RCURLY
cases     : cases case | 
case      : CASE expression COLON statements
          | DEFAULT COLON statements
expression: ID | INT
lvalue    : ID | DEFAULT

这里的关键是statement可能以令牌DEFAULT开头,而case也可能以令牌DEFAULT开头。现在,假设我们已经在解析中达到了以下几点:

switch ( <expression> ) { <cases> case <expression> : <statements>

所以我们正处于switch复合声明的中间;我们已经看过case 0:,我们正在处理一系列陈述。当前状态包括项目(还有一些项目;我只包括相关项目):

1. statements: statements · statement
2. case      : CASE expression COLON statements ·
3. statement : · assign SEMICOLON
4. assign    : · lvalue EQUALS expression
5. lvalue    : · DEFAULT

为第2项设置的前瞻是[ RCURLY, DEFAULT, ID ]

现在假设下一个标记是 default 。如果 default 后跟 = ,我们可以查看语句的开头。或者我们可以查看一个新的case子句,如果 default 后跟。但我们看不到未来的两个代币,只有一个代币;下一个标记是 default ,这就是我们所知道的。

但我们需要做出决定:

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

    < / LI>
  • 如果默认是案例的开头,我们需要将CASE expression COLON statements缩减为case(第2项)。在我们最终移动默认之前,我们会将cases case缩减为cases。然后我们将移动并继续DEFAULT COLON statements

与大多数LR解析器生成器一样,PLY解决了shift / reduce冲突,转而支持shift,因此它总是采用上面两个选项中的第一个。如果它然后看到而不是 = ,它将报告语法错误。

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

使用EBNF运算符|*+(交替,可选重复和重复)的交换机主体是:

switch-body -> (("case" expression | "default") ":" statement*)+

或者,为了减少麻烦:

case-header -> ("case" expression | "default") ":"
switch-body -> (case-header statement*)+

从接受的字符串的角度来看,这与

完全相同
switch-body -> case-header (case-header | statement)*

换句话说,是case-headerstatement s的一系列事物,其中第一个是case-header

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

从好的方面来看,它具有不强制解析器决定案件原因何时终止的优点。 (语法不再有case子句。)所以这是一个简单的LR(1)语法:

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

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

但实际上修复解析树并不是特别复杂,因为switch_body的每次缩减都清楚地表明了我们正在添加的内容。如果我们要添加一个case-header,我们可以在case子句的累积列表中添加一个新列表;如果它是一个陈述,我们将该陈述附加到最后一个案例子句的末尾。

所以我们可以在PLY中大致写出以下规则:

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', [])