【龙书笔记】用Python实现一个简单数学表达式从中缀到后缀语法的翻译器(采用递归下降分析法)

上篇笔记介绍了语法分析相关的一些基础概念,本篇笔记根据龙书第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节点的子节点。

以此类推,最终生成的语法分析树如下图所示:

2. 递归下降分析法(Recursion-Descent Parsing

递归下降分析法是一种自顶向下的语法分析法,它使用一组递归过程来处理输入串。文法产生式中的每个非终结符均有一个与之关联的过程或函数

预测分析法(Predictive Parsing)是一种简单的递归下降分析法,在预测分析法中,每个非终结符号对应的过程或函数中的控制流可以由lookahead符号无二义性地确定,即采用预测分析法时,扫描输入串的过程不需要回溯(backtracking)。分析输入串时出现的过程调用序列隐式地定义了该输入串的一颗语法分析树。

本文后面给出的简单表达式的后缀语法翻译器就是采用预测分析法实现的。

3. 左/右递归(Left/Right Recursion)

当一个文法产生式左侧的非终结符与右侧的产生式体开始处的非终结符相同时,就可能发生左递归。具有下面形式的产生式是典型的左递归产生式:

因为该产生式中,非终结符A又出现在产生式体的最左端,在采用递归下降分析法推导这个产生式时,可能会导致无限循环调用,如下图所示。

同理,下面的产生式存在右递归问题:

在采用递归下降分析法推导上面的产生式时,可能会产生右递归无限循环:

所以在使用递归下降分析法前,通常需要对原文法产生式做修改,以便消除左/右递归问题。wikipedia的Left recursion条目总结了一些典型的消除左递归的方法,感兴趣的话,可以去探究。

上面介绍的是略显枯燥的基础概念,下面开始本篇笔记的正题—用Python实现一个简单数学表达式的后缀语法翻译器。

我们的目标是把简单数学表达式的中缀形式翻译成后缀形式,假设给定的语法制导定义如下(其中,左边为文法产生式,右边为附加的语义规则,这些规则定义了从infix到postfix转换的语义规则):

上述语法制导定义对应的语法制导翻译计划如下:

由于上面翻译计划的产生式存在左递归问题(由非终结符expr引起),所以需要做调整以便消除左递归,调整后的语法制导翻译计划如下:

针对上述翻译计划,采用预测分析法,根据龙书第2.5节描述的算法流程,实现了Python版本的简单数学表达式从中缀到后缀语法的翻译器,完整的代码如下。

#!/bin/env python

'''
This demo is inspired by Section 2.5 of the 'Dragon Book': <Compilers: Principles, Techniques, and Tools>
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 ========================

时间: 2024-10-19 13:08:47

【龙书笔记】用Python实现一个简单数学表达式从中缀到后缀语法的翻译器(采用递归下降分析法)的相关文章

python中一个简单的webserver

python中一个简单的webserver 2013-02-24 15:37:49 分类: Python/Ruby 支持多线程的webserver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #!/usr/bin/python from SocketServer import ThreadingMixIn from BaseHTTPServer import HTTPServer,BaseHTTPRequestHandler cla

【Python】一个简单的例子

问题描述: Python基础篇 参考资料: (1)http://www.cnblogs.com/octobershiner/archive/2012/12/04/2801670.html (2)http://www.cnblogs.com/itech/archive/2010/06/20/1760345.html 例子: 求解Fibonacci glb_var.py gl_count=1 path.py # coding:utf-8 ''' Created on 2014-4-28 @autho

【译】使用python创建一个简单的restful风格的webservice应用

这是一个如何使用python快速构建简单restful风格webservice的应用教程. 1.分析rest路由规则 rest风格的服务通常使用web.py来创建服务器端脚本,一般情况下包含两个url路径: 一个是为了查询所有用户,一个是为了查询单个用户. 例如下面的url: http://localhost:8080/users http://localhost:8080/users/{id} 2.搭建web.py环境 首先你应该安装web.py模块到你的python环境下.如果你之前没有的话

编译器--简单数学表达式计算器

做了一个能够计算简单数学表达式值的小计算器,算不上是编译器,但用到了编译器的知识.最近在看一些编译器的东西,所以动手写这个最简单的计算器,既是对那些抽象的编译器知识有个形象的认识,也为后面添加复杂的东西--语句打下基础.此计算器是以<编译原理与实践>中实现的tiny编译器为参考写的,tiny是一个值得去研究的编译器,可以说是麻雀虽小,五脏俱全.从词法分析到代码生成都有,并且代码非常清晰易懂.我觉得想要了解编译器,可以从tiny入手,去将它跑起来并分析.废话不多说,开始记录这个小计算器. 先说下

【龙书笔记】语法分析涉及的基础概念简介

本篇笔记是我对龙书第2.3-2.5节内容的理解,主要介绍编译器前端关于语法分析的众多基础概念.下篇笔记将根据本篇笔记的主要内容,实现一个针对简单表达式的后缀式语法翻译器Demo(原书中是java实例,我给出的将是逻辑一致的Python版本的实现). 1. 语法分析(Syntax Analysis) 简单来说,语法分析的任务是分析输入的符号字符串(string of symbols, 通常是词法分析产生的tokens)是否遵循某种语言在其上下文无关文法(context-free grammar)中

【龙书笔记】编译器简介及程序构建过程综述

备注:本文是近期重新阅读编译器经典教材<Compilers Principles, Techniques, & Tools>一书(又称DragonBook,龙书)的其中一篇读书笔记. 1. 什么是编译器 从本质来看,平时提到的"编程语言"其实都是一些助记符,用于向其他人或机器描述我们想要完成的逻辑运算.这些易于人类理解的语言想要被计算机理解并正确执行,就必须被转换成机器码,而完成这一转换过程的软件系统就是编译器. 简言之,编译器其实也是一个计算机程序,它可以读取用一

Directx10 龙书笔记- 雾效的实现

雾 : 说白了就是一层有颜色的(一般是白色,灰色) 蒙蒙的感觉 混在光线里面. 不管是白天还是黑天. 龙书里面是这样实现的: 1.  给雾定义一个颜色 float3 gFogColor={0.7f,0.7f,0.7f}; 2. 然后呢在顶点着色器里面计算出 顶点级别的雾化需要的颜色加成比例 加成比例: 意思是当距离摄像机,就是你的眼睛越远,雾的浓度越大,就是颜色越深. 根据这个得出一个(0,1)的和距离有关的比例系数 vout.fogLerp = saturate((d-gFogStart)/g

如何用python写一个简单的find命令

对一个运维来说可能会经常去查找目录下的一些文件是否存在,最常用的就是find命令,它不仅可以查找文件也可以查找目录,find命令用法 查找文件 [[email protected] opt]# find /usr/ -type f -name df /usr/bin/df 查找目录 [[email protected] opt]# find /usr/ -type d -name python /usr/share/gcc-4.8.2/python 现在就讲一些如何用python实现这个简单功能

通过python 构建一个简单的聊天服务器

构建一个 Python 聊天服务器 一个简单的聊天服务器 现在您已经了解了 Python 中基本的网络 API:接下来可以在一个简单的应用程序中应用这些知识了.在本节中,将构建一个简单的聊天服务器.使用 Telnet,客户机可以连接到 Python 聊天服务器上,并在全球范围内相互进行通信.提交到聊天服务器的消息可以由其他人进行查看(以及一些管理信息,例如客户机加入或离开聊天服务器).这个模型如图 1 所示. 图 1. 聊天服务器使用 select 方法来支持任意多个客户机 聊天服务器的一个重要