PLY解析器-意外行为

时间:2020-04-30 13:51:44

标签: python parsing ply

我正在使用PLY创建一个计算器。我希望能够计算数值,但也可以存储函数。

对于给定的任何函数,让我们说:fun(x) = x + 3我将它存储在字典中,其中fun(x)是键,而x+3是值。键和值都存储为字符串。

我可以像这样fun(x)调用该函数,它将返回x+3。在此示例中,我将值'x'替换为9(模拟函数调用:fun(9))。它会返回12。

所以,现在是我的问题:当我尝试将此函数的返回值添加到NUMBER:fun(x) + 2时,我会遇到语法错误,尽管2 + fun(x)可以正常工作!我一点都不明白。

这是我的简化代码:

import ply.yacc as yacc
import ply.lex as lex

################################## LEXER ################################

tokens = (
    'FUNCTION',
    'VARIABLE',
    'NUMBER',
)

t_VARIABLE      = r'[a-zA-Z]+'
t_FUNCTION      = r'[a-zA-Z]{2,}\(([a-zA-Z]+)\)'
literals    = '+='
t_ignore    = " \t"

def t_NUMBER(t):
    r'(?:\d+(?:\.\d*)?)'
    t.value = int(t.value)
    return t

def t_error(t):
    print('Illegal character \'{}\''.format(t.value[0]))
    t.lexer.skip(1)

################################## PARSER ################################

functions = {}

def p_operations(p):
    """ expression : first
    first : NUMBER
    first : VARIABLE
    first : function
    """
    p[0] = p[1]

def p_plus(p):
    """ first : first '+' expression """
    if isinstance(p[1], str) or isinstance(p[3], str):
        p[0] = str(p[1]) + p[2] + str(p[3])
    else:
        p[0] = p[1] + p[3]

def p_function_assignation(p):
    '''expression : FUNCTION '=' expression'''
    functions[p[1]] = p[3]
    p[0] = p[3]


def p_function_expression(p):
    '''function : FUNCTION '''
    for key in functions.keys():
        if p[1] == key:
            p[0] = parser.parse(functions[key].replace('x', '9')). # I'm simulating here : fun(9)  
            break
    else:
        print("Variable '{}' not found".format(p[1]))

def p_error(t):
    print("Syntax error!")

################################## MAIN #################################

lexer = lex.lex() 
parser = yacc.yacc()

while True:
    try:
        question = raw_input('>>> ')
    except:
        question = input('>>> ')

    answer = parser.parse(question)
    if answer is not None:
        print(answer)

要测试我的代码:

fun(x) = x + 3 =>将函数存储到字典中

fun(x) =>正确打印12(9 + 3)

2 + fun(x) =>按原样打印14(2 + 12)

fun(x) + 2 =>收到错误消息!

1 个答案:

答案 0 :(得分:1)

在您的p_function_expression内部,您递归调用parse方法:

p[0] = parser.parse(functions[key].replace('x', '9'))

(注意:在您输入的问题中,该行在最后的括号后出现了来自多余的.的语法错误。)

parse的调用隐式使用了全局词法分析器;即lex.lexer的当前值(这是lex.lex()创建的最后一个词法分析器)。但是Ply词法分析器(实际上是大多数词法分析器)是stateful;词法分析器维护当前输入字符串和当前输入位置,以及一些其他信息(例如,如果您使用multiple states,则为当前词法分析器状态)。

因此,对parse的递归调用使(全局)词法分析器指向字符串的末尾。因此,当外部parse尝试读取下一个标记(实际上是第二个下一个标记,因为它已经具有超前标记)时,它会从词法分析器获取EOF,从而产生语法错误。

您可以通过启用parser debugging来看到此内容:

answer = parser.parse(question, debug=True)

此问题在Ply manual中进行了简要说明,其中指出您应该clone使用词法分析器进行可重入词法分析。

不幸的是,Ply手册没有提到Ply parser 对象也不是可重入的。在解析器的情况下,可重入问题不太明显。它们实际上仅适用于语法错误恢复(除非您将自己的持久数据存储在解析器对象中,允许这样做)。解析器没有克隆方法,主要是因为解析表已经过预先计算和缓存,因此创建新的解析器并不像创建新的词法分析器那样昂贵。

简而言之,您需要使用一个使用新词法分析器对象的新分析器对象进行内部解析:

p[0] = yacc.yacc().parse(functions[key].replace('x', '9'),
                         lexer=p.lexer.clone())

(解析器对象没有存储当前词法分析器的持久属性,但可以在传递给解析器操作函数的参数中以p.lexer的形式使用。请参见ever-helpful Ply manual。)

此外,您确实应该研究Python字典的用途。它们经过精确设计,因此您无需遍历所有条目即可找到所需的密钥。您可以通过简单的O(1)操作查找键。一个更简单的版本(如果您有多个功能,也可以更快):

def p_function_expression(p):
    '''function : FUNCTION '''
    if p[1] in functions:
        p[0] = yacc.yacc().parse(functions[p[1]].replace('x', '9'),
                                 lexer=p.lexer.clone())
    else:
        print("Variable '{}' not found".format(p[1]))

这会两次查询函数名称,仍然只是两个线性运算。但是,如果只想查找一次,则可以使用字典的get方法,该方法将返回默认值:

def p_function_expression(p):
    '''function : FUNCTION '''
    funcbody = functions.get(p[1], None)
    if funcbody is not None:
        p[0] = yacc.yacc().parse(funcbody.replace('x', '9'),
                                 lexer=p.lexer.clone())
    else:
        print("Variable '{}' not found".format(p[1]))

您还可以通过在捕获try的{​​{1}}块中查找名称来实现此目的。

我认为这应该是不是完成您自己设定的任务的好方法。将功能体表示为预先准备的AST更好,以后可以使用实际参数对其进行评估。在此模型中,您实际上根本不需要立即评估; 所有都被解析为AST,并且(如果)要评估解析后的文本时,可以调用AST的KeyError方法。