[英]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.