假設你希望實現上面的簡單算術表達式的語法分析,代碼如下:
~~~
# Yacc example
import ply.yacc as yacc
# Get the token map from the lexer. This is required.
from calclex import tokens
def p_expression_plus(p):
'expression : expression PLUS term'
p[0] = p[1] + p[3]
def p_expression_minus(p):
'expression : expression MINUS term'
p[0] = p[1] - p[3]
def p_expression_term(p):
'expression : term'
p[0] = p[1]
def p_term_times(p):
'term : term TIMES factor'
p[0] = p[1] * p[3]
def p_term_div(p):
'term : term DIVIDE factor'
p[0] = p[1] / p[3]
def p_term_factor(p):
'term : factor'
p[0] = p[1]
def p_factor_num(p):
'factor : NUMBER'
p[0] = p[1]
def p_factor_expr(p):
'factor : LPAREN expression RPAREN'
p[0] = p[2]
# Error rule for syntax errors
def p_error(p):
print "Syntax error in input!"
# Build the parser
parser = yacc.yacc()
while True:
try:
s = raw_input('calc > ')
except EOFError:
break
if not s: continue
result = parser.parse(s)
print result
~~~
在這個例子中,每個語法規則被定義成一個Python的方法,方法的文檔字符串描述了相應的上下文無關文法,方法的語句實現了對應規則的語義行為。每個方法接受一個單獨的p參數,p是一個包含有當前匹配語法的符號的序列,p[i]與語法符號的對應關系如下:
~~~
def p_expression_plus(p):
'expression : expression PLUS term'
# ^ ^ ^ ^
# p[0] p[1] p[2] p[3]
p[0] = p[1] + p[3]
~~~
其中,p[i]的值相當于詞法分析模塊中對p.value屬性賦的值,對于非終結符的值,將在歸約時由p[0]的賦值決定,這里的值可以是任何類型,當然,大多數情況下只是Python的簡單類型、元組或者類的實例。在這個例子中,我們依賴這樣一個事實:NUMBER標記的值保存的是整型值,所有規則的行為都是得到這些整型值的算術運算結果,并傳遞結果。
> 注意:在這里負數的下標有特殊意義–這里的p[-1]不等同于p[3]。詳見下面的嵌入式動作部分
在yacc中定義的第一個語法規則被默認為起始規則(這個例子中的第一個出現的expression規則)。一旦起始規則被分析器歸約,而且再無其他輸入,分析器終止,最后的值將返回(這個值將是起始規則的p[0])。注意:也可以通過在yacc()中使用start關鍵字參數來指定起始規則
p_error(p)規則用于捕獲語法錯誤。詳見處理語法錯誤部分
為了構建分析器,需要調用yacc.yacc()方法。這個方法查看整個當前模塊,然后試圖根據你提供的文法構建LR分析表。第一次執行yacc.yacc(),你會得到如下輸出:
~~~
$ python calcparse.py
Generating LALR tables
calc >
~~~
由于分析表的得出相對開銷較大(尤其包含大量的語法的情況下),分析表被寫入當前目錄的一個叫parsetab.py的文件中。除此之外,會生成一個調試文件parser.out。在接下來的執行中,yacc直到發現文法發生變化,才會重新生成分析表和parsetab.py文件,否則yacc會從parsetab.py中加載分析表。注:如果有必要的話這里輸出的文件名是可以改的。
如果在你的文法中有任何錯誤的話,yacc.py會產生調試信息,而且可能拋出異常。一些可以被檢測到的錯誤如下:
* 方法重復定義(在語法文件中具有相同名字的方法)
* 二義文法產生的移進-歸約和歸約-歸約沖突
* 指定了錯誤的文法
* 不可終止的遞歸(規則永遠無法終結)
* 未使用的規則或標記
* 未定義的規則或標記
下面幾個部分將更詳細的討論語法規則
這個例子的最后部分展示了如何執行由yacc()方法創建的分析器。你只需要簡單的調用parse(),并將輸入字符串作為參數就能運行分析器。它將運行所有的語法規則,并返回整個分析的結果,這個結果就是在起始規則中賦給p[0]的值。
- 0 一些翻譯約定
- 1 前言和預備
- 2 介紹
- 3 PLY概要
- 4 Lex
- 4.1 Lex的例子
- 4.2 標記列表
- 4.3 標記的規則
- 4.4 標記的值
- 4.5 丟棄標記
- 4.6 行號和位置信息
- 4.7 忽略字符
- 4.8 字面字符
- 4.9 錯誤處理
- 4.10 構建和使用lexer
- 4.11 @TOKEN裝飾器
- 4.12 優化模式
- 4.13 調試
- 4.14 其他方式定義詞法規則
- 4.15 額外狀態維護
- 4.16 Lexer克隆
- 4.17 Lexer的內部狀態
- 4.18 基于條件的掃描和啟動條件
- 4.19 其他問題
- 5 語法分析基礎
- 6 Yacc
- 6.1 一個例子
- 6.2 將語法規則合并
- 6.3 字面字符
- 6.4 空產生式
- 6.5 改變起始符號
- 6.6 處理二義文法
- 6.7 parser.out調試文件
- 6.8 處理語法錯誤
- 6.9 行號和位置的跟蹤
- 6.10 構造抽象語法樹
- 6.11 嵌入式動作
- 6.12 Yacc的其他
- 7 多個語法和詞法分析器
- 8 使用Python的優化模式
- 9 高級調試
- 9.1 調試lex()和yacc()命令
- 9.2 運行時調試
- 10 如何繼續