上篇笔记介绍了语法分析相关的一些基础概念,本篇笔记根据龙书第2.5节的内容实现一个针对简单表达式的后缀式语法翻译器Demo。
备注:原书中的demo是java实例,我给出的将是逻辑一致的Python版本的实现。
在简单后缀翻译器代码实现之前,还需要介绍几个基本概念。
1. 自顶向下分析法(top-down parsing)
顾名思义,top-down分析法的思路是推导产生式时,以产生式开始符号作为root节点,从上至下依次构建其子节点,最终构造出语法分析树。在具体实现时,它会把输入字符串从左到右依次扫描一遍,在扫描过程中构建分析树。
假设现有下面的一组文法产生式:
再假设现在要推导的输入字符串为:
for ( ; expr; expr; ) other
则top-down分析法的推导过程如下图所示:
采用的算法步骤为:
1) 以产生式开始符号(即非终结符号"stmt")作为root节点
2) 在标号为非终结符A的节点N上,选择A的一个产生式(在上述示例中,输入的是for语句,故选择for语句对应的产生式),并为该产生式体中的各个符号构造出N的子节点
3) 寻找下一个节点来构造子树,通常选的是语法分析树最左边的尚未扩展的非终结符
4) 重复第2-3步,直至输入串扫描完毕
在算法实现时,有一个重要的约定术语叫做lookahead symbol,它是指输入串中当前正在被扫描的终结符,我们经常会在语法扫描的实现代码中看到lookahead变量,所以有必要知道其来历。
根据上述算法流程对前面推导for语句的示意图做简单说明:
1) 选择产生式开始符号stmt作为root节点
2) 由于输入的是for语句,故选择产生式"for(optexpr; optexpr; optexpr) stmt"进行推导,此时lookahead符号指向终结符号"for",如上图最上面部分的(a)所示。用for产生式中的符号构造root节点的子节点,构造后的分析树如上图中部的(b)所示。
3) root节点的子节点已经构造完,根据算法流程,现在开始构造子节点的子树,虽然"for"节点是当前分析树最左端的尚未扩展的节点,但它是终结符无法扩展子树,此时,若当前被考虑的终结符号与lookahead符号匹配(在本例中都指向"for",正好是匹配的),那么图中语法分析树的箭头和输入串的箭头都要向前推进一步,即语法分析树的节点指向"("节点,lookahead符号指向输入串中的"("。由于"("又是个终结符且与lookahead符号匹配,故再次向前推进一步,此时,语法分析树箭头指向标号为optexpr的非终结符节点,而lookahead符号指向了输入串的终结符";",由于lookahead符号指向的";"与以optexpr开始的产生式无法匹配,故使用空产生式扩展optexpr节点的子节点。
以此类推,最终生成的语法分析树如下图所示:
3. 左/右递归(Left/Right Recursion)
当一个文法产生式左侧的非终结符与右侧的产生式体开始处的非终结符相同时,就可能发生左递归。具有下面形式的产生式是典型的左递归产生式:
因为该产生式中,非终结符A又出现在产生式体的最左端,在采用递归下降分析法推导这个产生式时,可能会导致无限循环调用,如下图所示。
同理,下面的产生式存在右递归问题:
在采用递归下降分析法推导上面的产生式时,可能会产生右递归无限循环:
所以在使用递归下降分析法前,通常需要对原文法产生式做修改,以便消除左/右递归问题。wikipedia的Left recursion条目总结了一些典型的消除左递归的方法,感兴趣的话,可以去探究。
#!/bin/env python
'''
This demo is inspired by Section 2.5 of the 'Dragon Book':
It implements a syntax-directed translator for simple expressions like '1+2-3'
It translate infix expression into postfix form
'''
class Parser(object):
lookahead = ''
def __init__(self):
print 'Please input an expression with infix form (One Character Per Line):'
Parser.lookahead = raw_input()
self.infix_list = [Parser.lookahead]
self.postfix_list = []
def expr(self):
self.term()
while True:
if Parser.lookahead == '+':
self.match('+')
self.term()
self.postfix_list.append('+')
elif Parser.lookahead == '-':
self.match('-')
self.term()
self.postfix_list.append('-')
else:
print 'raw input is (infix form):'
print ''.join(self.infix_list)
print 'postfix form of the input is:'
print ''.join(self.postfix_list)
return
def term(self):
if self._isdigits(Parser.lookahead):
self.postfix_list.append(Parser.lookahead)
self.match(Parser.lookahead)
else:
print 'term: syntax error'
def match(self, t):
if Parser.lookahead == t:
Parser.lookahead = raw_input()
self.infix_list.append(Parser.lookahead)
else:
print 'match: syntax error'
def _isdigits(self, s):
try:
int(s)
return True
except Exception, e:
return False
class Postfix(object):
def main(self):
parser = Parser()
parser.expr()
print '\n'
if '__main__' == __name__:
postfix = Postfix()
postfix.main()
运行上述脚本,输入合法的简单数学运算(
由产生式可知,目前只支持0-9内的数学加减法)中缀表达式,脚本会将其翻译为后缀语法。交互示例如下:
>>>
Please input an expression with infix form (One Character Per Line):
1
+
2
-
3
-
6
+
5
raw input is (infix form):
1+2-3-6+5
postfix form of the input is:
12+3-6-5+
>>>
备注:脚本目的在于示例如何用预测分析法对输入串做语法翻译,所以实现比较简单粗暴(如输入中缀表达式时每行只能输入一个字符,否则会报错 -_-)。
【参考资料】
1. 龙书第2.3-2.5节
2. wikipedia: Recursive descent parser
3. wikipedia: Left Recursion
========================= EOF ========================