Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenDocCN
lmpythw-zh
提交
450f66f3
L
lmpythw-zh
项目概览
OpenDocCN
/
lmpythw-zh
8 个月 前同步成功
通知
0
Star
18
Fork
5
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
L
lmpythw-zh
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
450f66f3
编写于
8月 13, 2017
作者:
W
wizardforcel
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
ex33.
上级
4ee1c0ec
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
80 addition
and
0 deletion
+80
-0
ex33.md
ex33.md
+80
-0
未找到文件。
ex33.md
0 → 100644
浏览文件 @
450f66f3
# 练习 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`
。
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录