造个轮子用Python写个解释器

2023年 8月 18日 62.7k 0

前言

没错今天提前来做这个东西,昨天晚上干这个玩意差不多干了两个多小时才搞定,导致凌晨2点才睡觉,最要命的是,写着写着突然想到有一道线代理解错了,一个晚上,做梦全是这两个东西。尤其是晚上效率比较低,所以把比较费脑子的活放在白天进行,晚上就背背书,背背公式,概念就好了。

那么今天的话,实现的效果是比较简单的效果,就是这个:
在这里插入图片描述
对,我们接下来要做的就是,实现这个家伙的解释器。当然还是那句话,目前实现的是非常简陋的东西,因此,这里目前我们做的还只是对数学运算的解析。

然后解析运行即可。

流程

那么同样的,我们可以先来看到我们的流程:
在这里插入图片描述
这个流程的话,图上面说的很清楚了,我这里就不进行阐述了。那么之后的话,我们今天要做的就是我们的这个解释器部分的内容如何实现,

这里的话我们昨天是得到了一个这样的树(注意:我们这里还只是得到了一个只能解析数学表达的解释器)

你可以看到这个就是当前的一个执行函数,可以看到就是这个流程。

def run(fn, text):
    # 解析词法,生成Token
    lexer = Lexer(fn, text)
    tokens, error = lexer.make_tokens()
    if error: return None, error

    # 解析基本的语法树AST
    parser = Parser(tokens)
    ast = parser.parse()
    if ast.error: return None, ast.error

    # 通过解释器运行程序
    interpreter = Interpreter()
    context = Context('')
    result = interpreter.visit(ast.node, context)

    return result.value, result.error

数学解释器

okey, 那么现在的话,我们来把我们的精力放在我们的这个解释器的实现上面。
我们的解释器要实现的功能其实很简单,那就是,通过我们的这个解析器得到的语法树,然后从根节点出发进行解析运算。例如我们昨天给的例子:

                *
               / 
              +   2
             / 
            5   3

我们的这个解析器得到的,这个玩意其实就是这个棵树,然后的话在这里插入图片描述

可以看到这个ast.node 得到的就是那个案例的+节点。所以的话,我们的工作就很明确了,从这个节点出发得到左右子树的值,这里是+,所以得到左右子树的结果,然后作为加法,把这个加载一起就好了。

结果封装

那么同样的,我们也要去封装这个运算的结果,把节点和当前是否存在异常拿到。

class RTResult:
    def __init__(self):
        self.value = None
        self.error = None

    def register(self, res):
        if res.error: self.error = res.error
        return res.value

    def success(self, value):
        self.value = value
        return self

    def failure(self, error):
        self.error = error
        return self

数的操作

之后的话,我们还要去实现这个数字的操作:
在这里插入图片描述

在这了的话,我们定义了基本的操作,接下来解析到不同的节点,我们就执行不同的操作,因为我们对这个数字的运算这里定义了四个基本的法则。

同时的话,为了便于进行操作和定位,我们这里还定义了这个东西:

class Context:
    def __init__(self, display_name, parent=None, parent_entry_pos=None):
        self.display_name = display_name
        self.parent = parent
        self.parent_entry_pos = parent_entry_pos

这里的话主要就是为了方便定位异常的。

运行时异常

之后的话,我们在这里定义了我们的运行时异常。

class RTError(HlangError):
    def __init__(self, pos_start, pos_end, details, context):
        super().__init__(pos_start, pos_end, 'Runtime Error:运行异常', details)
        self.context = context

    def as_string(self):
        result = self.generate_traceback()
        result += f'{self.error_name}: {self.details}'
        result += 'nn' + string_with_arrows(self.pos_start.ftxt, self.pos_start, self.pos_end)
        return result

    def generate_traceback(self):
        result = ''
        pos = self.pos_start
        ctx = self.context

        while ctx:
            result = f'  File {pos.fn}, line {str(pos.ln + 1)}, in {ctx.display_name}n' + result
            pos = ctx.parent_entry_pos
            ctx = ctx.parent

        return 'Traceback (most recent call last) 最后一次执行发生的异常是:n' + result

在递归执行的过程当中,出现运行异常进行处理。为了更加精准地提示异常,所以的话,这里有一个Context,用来定位错位,当前节点运算执行,异常,上一个节点是啥。也就是说为了实现这个:
在这里插入图片描述

运行解释实现

okey,那么之后的话,就是我们这边稍微麻烦一点的这个解释实现了。

我们的目的是啥,呢,我们首先回到我们的这个树:

                *
               / 
              +   2
             / 
            5   3

我们的目的是从根节点出发,得到左边+的结果,然后把2加起来,最终得到结果。

所以接下来的这段代码这样去想:(同样的,这里的递归我很难给你解释清楚,理解递归,想递归是有套路的,建议去看看我写的算法博文,推荐搜索专题刷题和爆肝7W蓝桥杯算法。

这里的话,我只说思路:

这里本质上就是一个中序遍历!

从这里进去:
visit的作用就是,访问当前的节点,然后执行得到当前节点的结果

然后由于我们的语法树里面,有不同的节点,所以的话,我们需要通过不同的节点来执行不同的方法。
在这里插入图片描述

在这里的话,对不同的节点有不同的执行方法:
在这里插入图片描述

然后这里考虑到这几种情况:
如果当前的节点是这样的:
在这里插入图片描述
那么有左右子树需要进行处理,
这个时候,就去再查看到左右子树,然后拿到结果:

这里看到visit被递归调用了。
在这里插入图片描述

然后如果是,只有一颗树的情况:访问一边然后拿到结果即可:
在这里插入图片描述
如果是数字的话,那么直接返回,当前的值就是结果:
在这里插入图片描述
流程其实不难,只是有很多额外的信息需要维护。

转化为leetcode的题目,估计也就是个中低档题目。

总结

目前的话,我现在实现了一个简单的解释器,那么接下来我们继续实现比较复杂的操作,最后的话,将这个进行开源。(虽然我很想把现在的代码贴出来,但是这个没办法,文章的代码内容不能超过60%,这里见谅)

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论