当前位置: 代码迷 >> python >> 如何修改解析器计算器语法以继续对先前结果进行计算?
  详细解决方案

如何修改解析器计算器语法以继续对先前结果进行计算?

热度:53   发布时间:2023-07-14 08:58:34.0

我正在尝试使用PLY在Python中创建解析器计算器。 我从一些PLY示例代码开始,然后从那里开始。 我要添加的功能是用于继续对先前结果进行计算的功能。 因此,如果您输入“ 4 + 5”,则结果为9。如果您输入“ * 2-3”,则新结果应为15,但对于我的代码,结果为-9,因为它首先解析“ 2-3”,什么时候应该先解析“ 9 * 2”。 当对上一个结果进行计算时,我将乘法或除法用作第一个运算符时,会发生此问题。

如我的代码摘录所示,我尝试将使用先前结果的表达式赋予优先级,但仍然存在相同的问题。

“ r”是存储先前结果的变量。

tokens = (
    'NUMBER',
)

literals = ['=', '+', '-', '*', '/', '(', ')']

precedence = (
    ('left', '+', '-'),
    ('right', 'RADD', 'RSUB'),
    ('left', '*', '/'),
    ('right', 'RMUL', 'RDIV'),
    ('right', 'UMINUS'),
)

def p_statement_expr(p):
    'statement : expression'
    p[0] = p[1]

def p_expression_binop(p):
    '''expression : expression '+' expression
                  | expression '-' expression
                  | expression '*' expression
                  | expression '/' expression'''
    if p[2] == '+':
        p[0] = p[1] + p[3]
    elif p[2] == '-':
        p[0] = p[1] - p[3]
    elif p[2] == '*':
        p[0] = p[1] * p[3]
    elif p[2] == '/':
        p[0] = p[1] / p[3]

def p_expression_cont(p):
    '''statement : '+' expression %prec RADD
                  | '-' expression %prec RSUB
                  | '*' expression %prec RMUL
                  | '/' expression %prec RDIV '''
    if p[1] == '+':
        p[0] = r + p[2]
    elif p[1] == '-':
        p[0] = r - p[2]
    elif p[1] == '*':
        p[0] = r * p[2]
    elif p[1] == '/':
        p[0] = r / p[2]   

def p_expression_uminus(p):
    "expression : '(' '-' expression ')' %prec UMINUS"

def p_expression_group(p):
    "expression : '(' expression ')'"
    p[0] = p[2]

def p_expression_number(p):
    "expression : NUMBER"
    p[0] = p[1]

我还尝试将语法更改为

p_expression_cont(p):
'''expression : '+' expression %prec MORADD
              | '-' expression %prec MORSUB
              | '*' expression %prec MORMUL
              | '/' expression %prec MORDIV '''

它解决了我最初的问题,但是现在像“ ++-* ++ / 23”这样的东西是可解析的,我显然不希望这样。 如何修改语法,以便在使用先前的结果时得到正确的计算?

使用PLY(或任何其他类似yacc的解析器生成器)很容易做到这一点,但重要的是要对要解析的事物的性质有所了解。

直观地,应该像* 2 - 3这样的连续行被解析为好像写为<prev> * 2 - 3其中<prev>是一个非终结符,以某种方式表示先前的值。

对于一个简单的例子,我们可以定义

expression : '$'

因此先前的值由显式标记$ 这样其余的语法就可以了。

当然,这样做的目的是用户不必键入$ (尽管,实际上,他们可能会发现它很有用。请参见下文。)因此,我们需要

expression: prev

其中非终端prev必须代表空令牌序列。 非终端由零个输入令牌表示是没有问题的,但是显然我们需要限制这种情况的发生。 所以我们不能只添加:

prev :

语法上,因为这将导致您已经遇到的问题:它使2**4有效,就好像它是2*<prev>*4

显然,我们希望语法仅在语句开始时制造一个空的<prev> 剩下的只是弄清楚如何做到这一点。

假设我们有两种expression :一种允许左侧为空,另一种则不允许。 我们将如何定义这两个非终端?

第二个只是通常的定义:

expression : expression '+' expression
expression : expression '-' expression
expression : expression '*' expression
expression : expression '/' expression
expression : '-' atom                    # See note below
expression : atom
atom : NUMBER
atom : '(' expression ')'

(出于以下原因,我分离出了atom产生)。

现在,那些左操作数可能是隐式的表达式呢? 这些非常相似:

expr_cont : expr_cont '+' expression
expr_cont : expr_cont '-' expression
expr_cont : expr_cont '*' expression
expr_cont : expr_cont '/' expression
expr_cont : atom

但是还有一种情况:

expr_cont :

在前四个生成中,我们要说的是在带有运算符的expr_cont ,右侧操作数必须是一个普通expression ,但是左侧操作数可以是具有隐式左侧操作数的表达式。 在最后一个生成中,我们允许隐式的左手操作数(但仅适用于此类表达式)。 显式的左操作数-数字和带括号的表达式-相同,因此我们可以重复使用上面创建的非末端atom


关于一元减号的注意事项:在原始语法中,一元减号仅在括号内允许使用,并且其优先级不正确。 原始产品是:

expression: '(' '-' expression ')' %prec UMINUS

在此生产中,优先声明是完全没有意义的,因为生产本身永远不会模棱两可。 (它明确地由括号包围,因此在遇到右括号时必须将其减少。)括号中一元减号的应用也是明确的,但不正确:生产有效地指定一元减号适用于整个表达式括在圆括号中,因此(-4 - 2)计算结果为-2(-(4-2)),而不是预期的-6。

上述语法中的简单修正方法明确表明,一元减的操作数是一个atom ,而不是expression 因此, -4 - 2必须解析为(-4) - 2 ,因为4 - 2不能是atom 但是,该修补程序消除了对以一元减开头的表达式加括号的要求。

一元负产生仅在expression而不是expr_cont意味着将以负号开头的连续表达式将被这样解析,这大概是限制的目的。


现在实现很简单:

precedence = (
    ('left', '+', '-'),
    ('left', '*', '/'),
)

# We can use the same function for all unit productions which pass
# through the semantic value of the right-hand symbol.
# (A unit production is one whose right-hand side has a single symbol.)
def p_unit(p):
    '''statement  : expr_cont
       expression : atom
       expr_cont  : atom
       atom       : NUMBER
    '''
    p[0] = p[1]

# Personally, I would write this as four functions, one for each operator,
# rather than executing the chain of if statements every time.
def p_expression_binop(p):
    '''expression : expression '+' expression
                  | expression '-' expression
                  | expression '*' expression
                  | expression '/' expression
       expr_cont  : expr_cont  '+' expression
                  | expr_cont  '-' expression
                  | expr_cont  '*' expression
                  | expr_cont  '/' expression

    '''
    if p[2] == '+':
        p[0] = p[1] + p[3]
    elif p[2] == '-':
        p[0] = p[1] - p[3]
    elif p[2] == '*':
        p[0] = p[1] * p[3]
    elif p[2] == '/':
        p[0] = p[1] / p[3]

def p_expression_uminus(p):
    '''expresion : '-' atom
    '''
    p[0] = -p[2]

def p_atom_group(p):
    '''atom : '(' expression ')'
    '''
    p[0] = p[2]

def p_expr_previous(p):
    '''expr_cont : 
    '''
    p[0] = r   # "previous" would be a better variable name than r

如上所述,在某些情况下,用户会发现用一种明确的方式写入“先前值”很方便。 例如,假设他们想查看<prev>?+1 或者假设他们只是想覆盖默认的算术优先级来计算(<prev> - 3) * 2 通过允许使用$作为显式的“先前值”令牌,可以很容易地提供这两种功能。 将其添加到文字列表之后,只需修改最后一个解析器函数:

def p_expr_previous(p):
    '''expr_cont  : 
                  | '$'
       expression : '$'
    '''
    p[0] = r   # "previous" would still be a better variable name than r
  相关解决方案