无法使用PLY解析elif

时间:2016-01-12 18:57:41

标签: python yacc bnf ply

我正在继续使用PLY解析。解析IFELSE IF语句时,我遇到了一个问题。

这是我的代码,我已经删除了不相关的部分,只留下了基础知识(这将在Python 2.7上运行)。

from ply import lex, yacc

tokens = [
    'ELSE',
    'IF',
    'LPAREN',
    'RPAREN',
    'LCURLY',
    'RCURLY'
    ]


t_IF = r'IF'
t_ELSE = r'ELSE'
t_LPAREN = r'\('
t_RPAREN = r'\)'
t_LCURLY = r'\{'
t_RCURLY = r'\}'
t_ignore = '\r\t '


def t_error(t):
    print t


def t_newline(t):
    r'\n+'
    t.lexer.lineno += len(t.value)


def p_error(p):
    print ('error', p)


def p_empty(p):
    'empty :'
    p[0] = None


def p_if_statement(p):
    'if_statement : IF LPAREN RPAREN LCURLY RCURLY elif_statements_or_empty else_statement_or_empty'
    p[0] = ('if_statement', (p[6]))
    print p[0]


def p_else_statement(p):
    'else_statement : ELSE LCURLY RCURLY'
    p[0] = ('else_statement')


def p_else_statement_or_empty(p):
    '''else_statement_or_empty : else_statement
                               | empty'''
    p[0] = p[1]


def p_elif_statement(p):
    'elif_statement : ELSE IF LPAREN RPAREN LCURLY RCURLY'
    p[0] = ('elif_statement')


def p_elif_statements_1(p):
    'elif_statements : elif_statement'
    p[0] = [p[1]]


def p_elif_statements_2(p):
    'elif_statements : elif_statements elif_statement'
    p[0] = p[1] + [p[2]]


def p_elif_statements_or_empty(p):
    '''elif_statements_or_empty : elif_statements
                                | empty'''
    p[0] = p[1]


lexer = lex.lex()

s = 'IF(){}ELSE IF(){}ELSE{}'

parser = yacc.yacc(start='if_statement')
parser.parse(s)  # ('error', LexToken(LCURLY,'{',1,21))

基本上,问题在于我正在搜索无限量的elif_statement s。当遇到ELSE {时,它认为存在解析错误,因为它期望在IF之后ELSE

这是什么叫“转移/减少错误”?有什么方法可以解决这个问题?

非常感谢任何帮助!

1 个答案:

答案 0 :(得分:1)

是的,这是转移/减少冲突的结果。 PLY警告你,你有两个,它们都与else条款有些相关。

但是,问题与else if子句的潜在无限序列无关。那不是问题。问题更加微妙,与您使用空制品有关。

从解决问题的角度来看,LR(1)最重要的方面是每个非终端需要减少(即,从右手创建)在紧接着的令牌移位之前。考虑到这一点,让我们来看看if_statement的语法:

if_statement : IF LPAREN RPAREN LCURLY RCURLY 
               elif_statements_or_empty
               else_statement_or_empty

并且假设我们已经看到IF(){},因此下一个标记是ELSE。此时右侧(此时是解析中唯一的活动项)我们知道下一个标记必须是ELSE或输入的结尾。在后一种情况下,明确必须做什么:必须减少empty,然后必须从elif_statements_or_empty减少empty;必须减少另一个empty,并且else_statement_or_empty必须从empty减少ELSE。这一切都有效。

但假设下一个标记为ELSE。怎么办?由于我们看不到IF以外的任何内容,因此我们不知道其后是(还是empty。但我们必须在两个选项之间做出决定:

  1. 减少 elif_statements_or_empty,然后ELSE。如果else_statement_or_emptyELSE的开头(在这种情况下不能为空),那将是正确的。

  2. Shift elif_statements_or_emptyelif_statements只能是ELSE的开头,不能为空,因此必须为ELSE IF。如果ELSEIF条款的一部分,那将是正确的。

  3. 这样就可以改变/减少冲突。 Bison总是选择转换/减少冲突的转变,因此它会假设ELSE IF之后的第一个ELSEIF(){}ELSE IF(){}子句的开头。让我们继续这个假设(在提供的输入的情况下恰好是正确的),并继续直到我们达到以下ELSE。我们现在看到了ELSE IF(){},我们再次盯着else_if_statement

    此时,绝对明确else_if_statement必须缩减为else_if_statements,同样明确ELSE IF必须缩减为ELSE。到现在为止还挺好。但现在出现了问题。如果有另一个else_if_statement,那就足够了,我们应该转移 ELSE成为以下else_statement的一部分。但如果else_if_statements是最终else_if_statements_or_empty的开头,那么我们需要我们刚创建的ELSE缩减为else_statement_or_empty,因为在我们改变开始else_statement_or_empty的{​​{1}}之前需要减少。

    再一次,野牛总是为这个转变而努力。但这意味着它无法决定开始elif_statements_or_empty

    那么,该怎么办?

    首先要注意的是empty生产实际上并没有添加任何价值。 (事实上​​,elif_statements制作也没有,我本来只是默默地消除,但我会留下这个括号。)如果我们只是将if_statement放在elif_statements中,那么我们就不需要在elif_statements_or_emptyelif_statements之间做出决定。当然,我们需要elif_statements: elif_statement | elif_statements elif_statement 来匹配一个空字符串,但这很简单:我们只为递归基本情况提供一个空的右侧。也就是说,而不是

    elif_statements: | elif_statements elif_statement
    

    我们将使用

    elif_statement

    我们现在匹配任何数字,包括零elif_statement,而不是匹配任何数字,但至少有一个elif_statements。这正是我们想要的。

    现在IF(){}基本情况为空,我们不再需要在elif_statements之后决定是否有空集elif_statements。我们只需减少一个空的elif_statement,然后看看下一步是else_statement还是def p_error(p): print ('error', p) # Fixed to return both elif and else statements def p_if_statement(p): 'if_statement : IF LPAREN RPAREN LCURLY RCURLY elif_statements else_statement' p[0] = ('if_statement', (p[6] + p[7])) print p[0] # This needs to return a list because it could be empty def p_else_statement(p): 'else_statement : ELSE LCURLY RCURLY' p[0] = ['else_statement'] def p_elif_statement(p): 'elif_statement : ELSE IF LPAREN RPAREN LCURLY RCURLY' p[0] = 'elif_statement' def p_elif_statements(p): 'elif_statements : elif_statements elif_statement' p[0] = p[1] + [p[2]] # This handles both empty elif_statements and an empty else # by reducing to an empty list def p_empty(p): '''elif_statements : else_statement :''' p[0] = [] 。因此,通过这种简化,我们实际上消除了两种移位减少冲突。

    总之,新的PLY文件:(仅解析器规则,没有其他更改):

    {{1}}