广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Python递归下降Parser怎么实现
  • 472
分享到

Python递归下降Parser怎么实现

Pythonparser 2023-05-17 07:05:27 472人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

1. 算术运算表达式求值要解析这类文本,需要另外一种特定的语法规则。我们这里介绍可以表示上下文无关文法(context free grammer)的语法规则巴科斯范式(BNF)和扩展巴科斯范式(EBNF)。从小到一个算术运算表达式,到大到几

    1. 算术运算表达式求值

    要解析这类文本,需要另外一种特定的语法规则。我们这里介绍可以表示上下文无关文法(context free grammer)的语法规则巴科斯范式(BNF)和扩展巴科斯范式(EBNF)。从小到一个算术运算表达式,到大到几乎所有程序设计语言,都是利用上下文无关文法来定义的。

    对于简单的算术运算表达式,假定我们已经用分词技术将其转化为输入的tokens流,如NUM+NUM*NUM(分词方法参见上一篇博文)。

    在此基础上,我们定义BNF规则定义如下:

    expr ::= expr + term
         | expr - term 
         | term
    term ::= term * factor
         | term / factor
         | factor
    factor ::= (expr)
         | NUM

    当然,这种计法还不够简洁明了,我们实际采用的为EBNF形式:

    expr ::= term { (+|-) term }*
    term ::= factor { (*|/) factor }*
    factor ::= (expr) 
           | NUM

    BNF和EBNF每一条规则(形如::=的式子)都可以看做是一种替换,即左侧的符号可以被右侧的符号所替换。我们在解析过程中尝试使用BNF/EBNF将输入文本与语法规则进行匹配,以完成各种替换和扩展。在EBNF中,被放置在{...}*内的规则是可选的,而*则表示可以重复零次或多次(类比于正则表达式)。

    下图形象地展示了递归下降解析器(parser)中“递归”和“下降”部分和ENBF的关系:

    Python递归下降Parser怎么实现

    在实际的解析过程中,我们对tokens流从左到右进行扫描,在扫描的过程中处理token,如果卡住就产生一个语法错误。每一条语法规则都被转化为一个函数或方法,例如上面的ENBF规则被转换成下述方法:

    class ExpressionEvaluator():
        ...
        def expr(self):
            ...
        def term(self):
            ...
        def factor(self):
            ...

    在调用某个规则对应方法的过程中,如果我们发现接下来的符号需要采用另一个规则来匹配,则我们就会“下降”到另一个规则方法(如在expr中调用term,term中调用factor),则也就是递归下降中“下降”的部分。

    有时也会调用已经在执行的方法(比如在expr中调用term,term中调用factor后,又在factor中调用expr,相当于一条衔尾蛇),这也就是递归下降中“递归”的部分。

    对于语法中出现的重复部分(例如expr ::= term { (+|-) term }*),我们则通过while循环来实现。

    下面我们来看具体的代码实现。首先是分词部分,我们参照上一篇介绍分词博客的代码。

    import re
    import collections
    
    # 定义匹配token的模式
    NUM = r'(?P<NUM>\d+)'  # \d表示匹配数字,+表示任意长度
    PLUS = r'(?P<PLUS>\+)'  # 注意转义
    MINUS = r'(?P<MINUS>-)'
    TIMES = r'(?P<TIMES>\*)'  # 注意转义
    DIVIDE = r'(?P<DIVIDE>/)'
    LPAREN = r'(?P<LPAREN>\()'  # 注意转义
    RPAREN = r'(?P<RPAREN>\))'  # 注意转义
    WS = r'(?P<WS>\s+)'  # 别忘记空格,\s表示空格,+表示任意长度
    
    master_pat = re.compile(
        '|'.join([NUM, PLUS, MINUS, TIMES, DIVIDE, LPAREN, RPAREN, WS]))
    
    # Tokenizer
    Token = collections.namedtuple('Token', ['type', 'value'])
    
    
    def generate_tokens(text):
        scanner = master_pat.scanner(text)
        for m in iter(scanner.match, None):
            tok = Token(m.lastgroup, m.group())
            if tok.type != 'WS':  # 过滤掉空格符
                yield tok

    下面是表达式求值器的具体实现:

    class ExpressionEvaluator():
        """ 递归下降的Parser实现,每个语法规则都对应一个方法,
        使用 ._accept()方法来测试并接受当前处理的token,不匹配不报错,
        使用 ._except()方法来测试当前处理的token,并在不匹配的时候抛出语法错误
        """
    
        def parse(self, text):
            """ 对外调用的接口 """
            self.tokens = generate_tokens(text)
            self.tok, self.next_tok = None, None  # 已匹配的最后一个token,下一个即将匹配的token
            self._next()  # 转到下一个token
            return self.expr()  # 开始递归
    
        def _next(self):
            """ 转到下一个token """
            self.tok, self.next_tok = self.next_tok, next(self.tokens, None)
    
        def _accept(self, tok_type):
            """ 如果下一个token与tok_type匹配,则转到下一个token """
            if self.next_tok and self.next_tok.type == tok_type:
                self._next()
                return True
            else:
                return False
    
        def _except(self, tok_type):
            """ 检查是否匹配,如果不匹配则抛出异常 """
            if not self._accept(tok_type):
                raise SyntaxError("Excepted"+tok_type)
    
        # 接下来是语法规则,每个语法规则对应一个方法
        
        def expr(self):
            """ 对应规则: expression ::= term { ('+'|'-') term }* """
            exprval = self.term() # 取第一项
            while self._accept("PLUS") or self._accept("DIVIDE"): # 如果下一项是"+"或"-"
                op = self.tok.type 
                # 再取下一项,即运算符右值
                right = self.term() 
                if op == "PLUS":
                    exprval += right
                elif op == "MINUS":
                    exprval -= right
            return exprval
                
        def term(self):
            """ 对应规则: term ::= factor { ('*'|'/') factor }* """
            
            termval = self.factor() # 取第一项
            while self._accept("TIMES") or self._accept("DIVIDE"): # 如果下一项是"+"或"-"
                op = self.tok.type 
                # 再取下一项,即运算符右值
                right = self.factor() 
                if op == "TIMES":
                    termval *= right
                elif op == "DIVIDE":
                    termval /= right
            return termval          
                
            
        def factor(self):
            """ 对应规则: factor ::= NUM | ( expr ) """
            if self._accept("NUM"): # 递归出口
                return int(self.tok.value)
            elif self._accept("LPAREN"):
                exprval = self.expr() # 继续递归下去求表达式值
                self._except("RPAREN") # 别忘记检查是否有右括号,没有则抛出异常
                return exprval
            else:
                raise SyntaxError("Expected NUMBER or LPAREN")

    我们输入以下表达式进行测试:

    e = ExpressionEvaluator()
    print(e.parse("2"))
    print(e.parse("2+3"))
    print(e.parse("2+3*4"))
    print(e.parse("2+(3+4)*5"))

    求值结果如下:

    2
    5
    14
    37

    如果我们输入的文本不符合语法规则:

    print(e.parse("2 + (3 + * 4)"))

    则会抛出SyntaxError异常:Expected NUMBER or LPAREN
    综上,可见我们的表达式求值算法运行正确。

    2. 生成表达式树

    上面我们是得到表达式的结果,但是如果我们想分析表达式的结构,生成一棵简单的表达式解析树呢?那么我们需要对上述类的方法做一定修改:

    class ExpressionTreeBuilder(ExpressionEvaluator):
        def expr(self):
                """ 对应规则: expression ::= term { ('+'|'-') term }* """
                exprval = self.term() # 取第一项
                while self._accept("PLUS") or self._accept("DIVIDE"): # 如果下一项是"+"或"-"
                    op = self.tok.type 
                    # 再取下一项,即运算符右值
                    right = self.term() 
                    if op == "PLUS":
                        exprval = ('+', exprval, right)
                    elif op == "MINUS":
                        exprval -= ('-', exprval, right)
                return exprval
        
        def term(self):
            """ 对应规则: term ::= factor { ('*'|'/') factor }* """
            
            termval = self.factor() # 取第一项
            while self._accept("TIMES") or self._accept("DIVIDE"): # 如果下一项是"+"或"-"
                op = self.tok.type 
                # 再取下一项,即运算符右值
                right = self.factor() 
                if op == "TIMES":
                    termval = ('*', termval, right)
                elif op == "DIVIDE":
                    termval = ('/', termval, right)
            return termval          
        
        def factor(self):
            """ 对应规则: factor ::= NUM | ( expr ) """
            if self._accept("NUM"): # 递归出口
                return int(self.tok.value) # 字符串转整形
            elif self._accept("LPAREN"):
                exprval = self.expr() # 继续递归下去求表达式值
                self._except("RPAREN") # 别忘记检查是否有右括号,没有则抛出异常
                return exprval
            else:
                raise SyntaxError("Expected NUMBER or LPAREN")

    输入下列表达式测试一下:

    print(e.parse("2+3"))
    print(e.parse("2+3*4"))
    print(e.parse("2+(3+4)*5"))
    print(e.parse('2+3+4'))

    以下是生成结果:

    ('+', 2, 3)
    ('+', 2, ('*', 3, 4))
    ('+', 2, ('*', ('+', 3, 4), 5))
    ('+', ('+', 2, 3), 4)

    可以看到表达式树生成正确。

    我们上面的这个例子非常简单,但递归下降的解析器也可以用来实现相当复杂的解析器,例如python代码就是通过一个递归下降解析器解析的。您要是对此跟感兴趣可以检查Python源码中的Grammar文件来一探究竟。然而,下面我们接着会看到,自己动手写一个解析器会面对各种陷阱和挑战。

    左递归和运算符优先级陷阱

    任何涉及左递归形式的语法规则,都没法用递归下降parser来解决。所谓左递归,即规则式子右侧最左边的符号是规则头,比如对于以下规则:

    items ::= items ',' item 
          | item

    完成该解析你可能会定义以下方法:

    def items(self):
        itemsval = self.items() # 取第一项,然而此处会无穷递归!
        if itemsval and self._accept(','):
            itemsval.append(self.item())
        else:
            itemsval = [self.item()]

    这样做会在第一行就无穷地调用self.items()从而产生无穷递归错误。

    还有一种是语法规则自身的错误,比如运算符优先级。我们如果忽视运算符优先级直接将表达式简化如下:

    expr ::= factor { ('+'|'-'|'*'|'/') factor }*
    factor ::= '(' expr ')'
           | NUM

    PYTHON 复制 全屏

    这个语法从技术上可以实现,但是没有遵守计算顺序约定,导致"3+4*5"的运算结果为35,而不是预期的23。因此,需要使用单独的expr和term规则来确保计算结果的正确性。

    以上就是Python递归下降Parser怎么实现的详细内容,更多请关注编程网其它相关文章!

    --结束END--

    本文标题: Python递归下降Parser怎么实现

    本文链接: https://www.lsjlt.com/news/210780.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档
    猜你喜欢
    • Python递归下降Parser怎么实现
      1. 算术运算表达式求值要解析这类文本,需要另外一种特定的语法规则。我们这里介绍可以表示上下文无关文法(context free grammer)的语法规则巴科斯范式(BNF)和扩展巴科斯范式(EBNF)。从小到一个算术运算表达式,到大到几...
      99+
      2023-05-17
      Python parser
    • Python技法之简单递归下降Parser的实现方法
      目录1. 算术运算表达式求值2. 生成表达式树左递归和运算符优先级陷阱3. 相关包参考总结1. 算术运算表达式求值 在上一篇博文《Python技法:用re模块实现简易tokenize...
      99+
      2022-11-10
    • python怎么实现递归
      这篇文章将为大家详细讲解有关python怎么实现递归,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。最简单的递归的实例  # -*- coding:ut...
      99+
      2022-10-19
    • python怎么实现梯度下降求解逻辑回归
      今天小编给大家分享一下python怎么实现梯度下降求解逻辑回归的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。线性回归1.线性...
      99+
      2023-07-06
    • python怎么实现尾递归优化
      小编给大家分享一下python怎么实现尾递归优化,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!说明尾递归是指在函数返回时调用自身,return语句不能包含表达式。...
      99+
      2023-06-20
    • python实现梯度下降求解逻辑回归
      本文实例为大家分享了python实现梯度下降求解逻辑回归的具体代码,供大家参考,具体内容如下 对比线性回归理解逻辑回归,主要包含回归函数,似然函数,梯度下降求解及代码实现 线性回归 ...
      99+
      2022-11-11
    • c语言递归和非递归排序怎么实现
      本篇内容主要讲解“c语言递归和非递归排序怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“c语言递归和非递归排序怎么实现”吧!递归代码流程归并就是把两个或多个序列合并,这里只介绍二路归并,就...
      99+
      2023-06-30
    • Python实现多元线性回归的梯度下降法
      目录1. 读取数据2.定义代价函数3. 梯度下降4.可视化展示1. 读取数据 首先要做的就是读取数据,请自行准备一组适合做多元回归的数据即可。这里以data.csv为例,这里做的是二...
      99+
      2022-11-11
    • python如何实现梯度下降求解逻辑回归
      线性回归1.线性回归函数似然函数的定义:给定联合样本值X下关于(未知)参数 的函数似然函数:什么样的参数跟我们的数据组合后恰好是真实值 2.线性回归似然函数对数似然: 3.线性回归目标函数(误差的表达式,我们的目的就是使得真实值与预...
      99+
      2023-05-14
      Python
    • php递归方法怎么实现
      本教程操作环境:windows7系统、PHP8.1版、Dell G3电脑。php递归方法怎么实现?递归的三种常用技法:静态变量、全局变量、引用一 静态变量方式function loop(){ static $i = 0; echo $i...
      99+
      2022-10-24
    • Python怎么用递归实现求二叉树深度
      本篇内容介绍了“Python怎么用递归实现求二叉树深度”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!其实就是求二叉树层级,比如一个单点就是一...
      99+
      2023-06-02
    • 怎么用python递归实现链表快速倒转
      这篇文章主要介绍“怎么用python递归实现链表快速倒转”,在日常操作中,相信很多人在怎么用python递归实现链表快速倒转问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么用python递归实现链表快速倒转...
      99+
      2023-06-30
    • python中逻辑回归随机梯度下降法怎么用
      这篇文章主要为大家展示了“python中逻辑回归随机梯度下降法怎么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“python中逻辑回归随机梯度下降法怎么用”这篇文章吧。随机梯度下降法随机梯度下...
      99+
      2023-06-25
    • PostgreSQL中怎么实现递归查询
      本篇文章给大家分享的是有关PostgreSQL中怎么实现递归查询,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。在内部,它是这样表示滴:&nbs...
      99+
      2022-10-18
    • MySQL中怎么实现递归查询
      本篇文章给大家分享的是有关MySQL中怎么实现递归查询,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Oracle 递归查询在 Oracle 中...
      99+
      2022-10-18
    • SQL中怎么实现递归查询
      SQL中怎么实现递归查询,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。 with cte as(selec&#...
      99+
      2022-10-18
    • Javascript尾递归编程怎么实现
      本篇内容介绍了“Javascript尾递归编程怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!尾递归编程思想递归是编程中必不可少的一环...
      99+
      2023-07-02
    • python递归函数斐波那契数列怎么实现
      斐波那契数列是一个数列,其中每个数字是前两个数字的和,即F(n) = F(n-1) + F(n-2)。递归函数可以用来实现斐波那契数...
      99+
      2023-09-26
      python
    • php怎么不递归实现遍历目录下所有文件
      这篇文章主要介绍php怎么不递归实现遍历目录下所有文件,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!实现方法:1、创建一个数组,将要遍历的这个目录放入;2、循环处理这个数组,循环结束的条件是数组为空;3、每次循环,处...
      99+
      2023-06-15
    • Oracle递归函数怎么用java实现
      在Java中,你可以通过创建一个递归函数来实现Oracle递归。以下是一个使用Java实现Oracle递归的示例:```javapu...
      99+
      2023-09-26
      Oracle java
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作