簡體   English   中英

如何修改解析器計算器語法以繼續對先前結果進行計算?

[英]How do I modify my parser calculator grammar to continue calculations on previous result?

我正在嘗試使用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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM