最新消息:欢迎光临 魔力 • Python!大家可以点开导航菜单中的【学习目录】,这个目录类似图书目录,更加方便学习!

一起来写个简单的解释器(2)

Python教程 小楼一夜听春语 1334浏览 0评论

本系列文章参考国外编程高手鲁斯兰的博客文章《Let’s Build A Simple Interpreter》。

上一篇文章中,我们一起实现了计算器的加法功能。

并且,在文末给大家留下了一些练习:

  • 让解释器支持减法运算;
  • 让解释器支持多位整数的运算,例如:12+36;
  • 添加一个方法,让解释器能够处理用户所输入表达式中的空格。

我建议大家先独自尝试完成这些练习,再继续本篇文章的学习。

当然,如果你如果已经尽力,但是没能够完成练习,那么,这篇文章将会给你带来很大的收获。

在开始这篇文章的主体内容之前,我先引用原文中的一个小故事。

这个故事它所包含的哲理适用于各行各业。

《高效思考的5个要素》的作者 Burger 和 Starbird 在书中分享了一个关于他们观看国际知名小号演奏家 Tony Plog 为多才多艺的小号演奏者举办大师课的故事。学生们首先演奏了复杂的音乐章节,他们表演的都很棒。但是,随后他们被要求表演非常基础、简单的音符。当他们演奏那些音符时,比起之前演奏的复杂乐章显得有些稚嫩。当他们完成演奏之后,大师也演奏了相同的音符。但大师演奏这些音符时听起来并不稚嫩,差异非常明显。Tony 解释道:掌握简单音符的演奏,让演奏者可以更完美地掌握复杂的乐章。

这个故事的结论:要想成为真正的艺术家,你必须集中精力掌握简单、基础的概念。

再精密的仪器也都是由简单的一个一个零件巧妙的组合而成。

在任何一个领域,你必须先掌握这些简单基础的概念,再通过灵活的运用才能够成为这个领域的高手。

接下来,我们再继续依靠我们所学过的Python3基础知识,来为我们的计算器或者说算术表达式的解释器添加更多的功能。

根据之前的练习要求,我们逐步来完成这些任务。

一、增加保存当前字符的变量

因为我们是逐个获取字符进行验证,一个多位数字需要多次获取才能够组成完整的数字生成记号,那么在记号生成之前,我们需要先使用一个变量保存字符,进行验证。

示例代码:(class Interpreter)

def __init__(self, text):
    ...省略其它代码...
    self.current_char = self.text[self.position] # 设置当前字符为指定位置的字符

二、增加获取下一个字符的方法

因为新增加的功能中表达式中字符的数量不再是3个,而是无法固定的数量。

数字部分可能是多位数字,并且表达式中可能包含任意数量的空格。

这样的话,我们需要对字符进行循环验证,会经常用到获取下一个字符的方法。

那么,经常用到的重复的方法,我们把它抽象成一个独立的方法。

示例代码:

def advance(self):  # 定义获取下一个字符的方法
    self.position += 1  # 获取字符的位置自增
    if self.position >= len(self.text):  # 如果位置到达字符串的末尾
        self.current_char = None  # 设置当前字符为None值
    else:  # 否则
        self.current_char = self.text[self.position]  # 设置当前字符为指定位置的字符

三、增加跳过空格的方法

如果遇到空格,无论是单个还是连续多个,我们都应该将其跳过。

示例代码:

def skip_whitespace(self):  # 定义跳过空格的方法
    while self.current_char is not None and self.current_char.isspace():  # 如果当前字符不是None值并且当前字符是空格
        self.advance()  # 获取下一个字符

四、增加获取多位数字的方法

如果获取到的字符是数字,无论是单个还是连续多个,我们都将它们连接起来,转为整数类型后返回。

示例代码:

def long_integer(self):  # 获取多位数字
    result = ''
    while self.current_char is not None and self.current_char.isdigit():  # 如果当前字符不是None值并且当前字符是数字
        result += self.current_char  # 连接数字
        self.advance()  # 获取下一个字符
    return int(result)  # 返回数字

五、修改词法分析器的代码

在新版本的词法分析器中,我们只需要根据当前字符的类型进行不同的处理。

示例代码:

def get_next_token(self):
    while self.current_char is not None:  # 如果当前字符不是None值
        if self.current_char.isspace():  # 如果当前字符是空格
            self.skip_whitespace()  # 跳过所有空格
            continue
        if self.current_char.isdigit():  # 如果当前字符是整数
            return Token(INTEGER, self.long_integer())  # 获取完整的数字创建记号对象并返回
        if self.current_char == '+':  # 如果当前字符是加号
            self.advance()  # 跳到下一字符
            return Token(PLUS, self.current_char)  # 创建记号对象并返回
        if self.current_char == '-':  # 如果当前字符是减号
            self.advance()  # 跳到下一字符
            return Token(MINUS, self.current_char)  # 创建记号对象并返回
        self.error()  # 如果以上都不是,则抛出异常。
    return Token(EOF, None)  # 遍历结束返回结束标识创建的记号对象

这一次,词法分析器的代码更加清晰。

六、修改表达式计算方法

这里我们添加减法的支持。

def expr(self):
    self.current_token = self.get_next_token()

    left = self.current_token
    self.eat(INTEGER)

    operator = self.current_token
    if operator.value_type == PLUS:  # 如果运算符的类型是加法
        self.eat(PLUS)  # 验证加法运算符
    else:  # 否则
        self.eat(MINUS)  # 验证减法运算符

    right = self.current_token
    self.eat(INTEGER)

    if operator.value_type == PLUS:  # 如果运算符的类型是加法
        result = left.value + right.value  # 进行加法运算
    else:  # 否则
        result = left.value - right.value  # 进行减法运算
    return result

上方代码中,添加注释的部分是更新的内容。

到这里,我们就完成了所有目标功能。

最后,我们仍然需要掌握一些概念。

在上一篇文章中,我们了解了在两个重要的概念: Token(记号) 和 Lexical Analyzer(词法分析器)。

这一篇文章中,我们来简单了解一下 lexeme(词位)、parsing(语法分析)和 parser(语法分析器)。

1、 lexeme(词位)

词位的中文解释是语言词汇的基本单位。

在这里,lexeme就是组成 token 的字符序列。

以当前的案例来说,词位就是数字、加号和减号:

记号 词位
INTEGER 1,12,666,69,8,0,3568
PLUS +
MINUS

2、parsing(语法分析)和 parser(语法分析器)。

我们所编写的代码中,“ expr()”方法是真正的对一个算术表达式进行解释的地方。

但是,在对一个算术表达式进行解释之前,我们需要先识别短语的类型,比如是加法表达式还是减法表达式。

“ expr()”方法本质上就是:先从“ get_next_token()” 方法获取token流,找到token流中的特定的结构,或者说识别出特定的短语,然后,解释识别出的短语,生成算术表达式的结果。

找到token流中特定结构的过程,或者说,识别token 流中特定短语的过程称之为Parsing(语法分析)。

解释器或编译器中完成语法分析的部分,叫做Parser(语法分析器)。

现在我们知道“ expr()”方法是解释器中既做了 Parsing(语法分析),又做了Interpreting(解释)的部分。

“ expr()”方法首先尝试识别(Parsing)token流中的[INTEGER>>PLUS>>INTEGER]结构或者[INTEGER>>MINUS>>INTEGER] 结构的短语,成功识别其中一种短语后,对短语进行解释,并将两整数相加或相减的结果返回。

以上就是《一起来写一个简单的解释器》系列文章的第二篇内容。

在这篇内容的基础上,大家可以尝试进行以下功能的扩展:

  • 让解释器支持乘法运算;
  • 让解释器支持除法运算;
  • 让解释器支持任意多加法和减法运算,例如:3+7-4-2+8。

而且,当学习完这篇文章,请大家自我检查一下,是否了解了以下内容:

  • 什么是 Lexeme(词位)?
  • 找出Token流中特定结构的过程叫什么?或者说,从 Token流中识别出特定短语的过程叫什么?
  • 解释器或编译器中做 parsing(语法分析)部分的叫什么?

项目源代码下载:【点此下载

转载请注明:魔力Python » 一起来写个简单的解释器(2)

头像
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网站 (可选)