我正在使用PLY为Unrealscript编写一个解析器,并且我(我希望)遇到了我设置的解析规则中最后一个含糊之处。
Unrealscript有一个关键字default
,根据上下文使用的关键字不同。在常规语句行中,您可以使用default
,如下所示:
default.SomeValue = 3; // sets the "default" class property to 3
当然,还有default
个switch
语句的案例标签:
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]
非常感谢有关如何解决此冲突的任何帮助或指导!提前谢谢。
答案 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
。
如果默认是案例的开头,我们需要将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-header
或statement
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', [])