1
0
mirror of https://github.com/apachecn/lmpythw-zh.git synced 2025-05-31 05:27:43 +00:00
lmpythw-zh/ex33.md
wizardforcel 450f66f3fd ex33.
2017-08-13 11:20:58 +08:00

81 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 练习 33解析器
想象一下,您将获得一个巨大的数字列表,您必须将其输入到电子表格中。一开始,这个巨大的列表只是一个空格分隔的原始数据流。你的大脑会自动在空格处拆分数字流并创建数字。你的大脑像扫描器一样。然后,您将获取每个数字,并将其输入到具有含义的行和列中。你的大脑像一个解析器,通过获取扁平的数字(记号),并将它们变成一个更有意义的行和列的二维网格。你遵循的规则,什么数字进入什么行什么列,是你的“语法”,解析器的工作就是像你对于电子表格那样使用语法。
我们再来看一下练习 32 中的微型 Python 代码,再从三个不同的角度讨论解析器:
```py
def hello(x, y):
print(x + y)
hello(10, 20)
```
当你查看这个代码时,你看到什么?我看到一棵树,类似于我们之前创建的`BSTree``TSTree`。你看到树了吗?我们从这个文件的最上方开始,学习如何将字符转换为树。
首先,当我们加载一个`.py`文件时,它只是一个“字符”流 - 实际上是字节,但 Python 使用Unicode所以必须处理字符。这些字符在一行中毫无结构扫描器的任务是增加第一层次的意义。扫描器通过使用正则表达式从字符串流中提取意义创建记号列表。我们已经将一个字符列表转换为一个记号列表但看看`def hello(x,y):`函数。这是一个函数,里面有代码块。这意味着某种形式的“包含”或“东西里面的东西”的结构。
一个很容易表示包含的方式是用一棵树。我们可以使用表格,像你的电子表格一样,但它并不像树那么容易。接下来看看`hello(x, y)`部分。我们有一个`NAME(hello)`记号,但是我们要抓取`(...)`部分的内容,并且知道它在括号内。再次,我们可以使用一个树,我们将`(...)`部分中的`x, y`部分“嵌套” 为树的子节点或分支。最终,我们就拥有了一棵树,从这个 Python 代码的根开始,并且每个代码块,`print`,函数定义和函数调用都是根的分支,它们也有子分支,以此类推。
为什么我们这样做?我们需要基于其语法,知道 Python 代码的结构,以便我们稍后分析。如果我们不将记号的线性列表转换成树结构,那么我们不知道函数,代码块,分支或表达式的边界在哪里。我们必须以“直线”方式在飞行中确定边界,这不容易使其可靠。很多早期的糟糕语言是直线语言,我们现在知道了他们不必须是这样。我们可以使用解析器构建树结构。
解析器的任务是从扫描器中获取记号列表,并将其翻译成更有意义的语法树。您可以认为解析器是,对记号流应用另一个正则表达式。扫描器的正则表达式将大量字符放入记号中。解析器的“正则表达式”将这些记号放在盒子里面,它里面有盒子,以此类推,直到记号不再是线性的。
解析器也为这些盒子添加了含义。解析器将简单地删除`()`括号记号,并为可能的`Function`类创建一个特殊的`parameters`列表。它会删除冒号,无用的空格,逗号,任何没有真正意义的记号,并将其转换为更易于处理的嵌套结构。最后的结果可能看起来像,上面的示例代码的伪造树:
```
* root
* Function
- name = hello
- parameters = x, y
- code:
* Call
- name = print
- parameters =
* Expression
- Add
- a = x
- b = y
* Call
- name = hello
- parameters = 10, 20
```
## 递归下降解析
有几种已建立的方法可以为这种语法创建解析器但最简单的方法称为递归下降解析器RDP。我实际上在我《笨办法学 Python》练习 49 中讲解了这个话题。您创建了一个简单的 RDP 解析器来处理您的小游戏语言,您甚至不了解它。在本练习中,我将对如何编写 RDP 解析器进行更正式的描述,然后让您使用我们上面的 Python 小代码片段来尝试它。
RDP 使用多个相互递归的函数调用它实现了给定语法的树形结构。RDP 解析器的代码看起来像您正在处理的实际语法只要遵循一些规则它们就很容易编写。RDP 解析器的两个缺点是:它们可能不是非常有效,并且通常需要手动编写它们,因此它们的错误比生成的解析器更多。对于 RDP 解析器可以解析的东西,还有一些理论上的限制,但是由于您手动编写它们,您通常可以解决很多限制。
为了编写一个 RDP 解析器,您需要使用三个主要操作,来处理扫描器的记号:
> `peek`
> 如果下一个记号能够匹配,返回它,但是不从流中移除。
> `match`
> 匹配下一个记号,并且从流中移除。
> `skip`
> 由于不需要下个记号,跳过它,将其从流中移除。
你会注意到,这些是我在练习 33 中让你为扫描器创建的三个操作,这就是为什么。你需要他们来实现一个 RDP 解析器。
您可以使用这三个函数来编写语法解析函数,从扫描仪中获取记号。这个练习的一个简短的例子是,解析这个简单的函数:
```py
def function_definition(tokens):
skip(tokens) # discard def
name = match(tokens, 'NAME')
match(tokens, 'LPAREN')
params = parameters(tokens)
match(tokens, 'RPAREN')
match(tokens, 'COLON')
return {'type': 'FUNCDEF', 'name': name, 'params': params}
```
你可以看到我只是接受记号并使用`match``skip`处理它们。您还会注意到我有一个`parameters`函数,它是“递归下降解析器”的“递归”部分。当它需要为函数解析参数时,`function_definition`会调用`parameters`