6.1 一个例子
最后更新于:2022-04-01 01:10:17
假设你希望实现上面的简单算术表达式的语法分析,代码如下:
~~~
# 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]的值。