编译器-1:简介

问候,

上周的技巧是一些娱乐时间,我们在其中构建了Sudoku求解器。 这个

一周,我们将构建一些复杂的东西:编译器。 编译器

构造是CS的一个困难分支,我不希望这篇文章成为

书的大小,因此我们必须使事情保持简单。

另一方面,整个事物或多或少必须具有实际用途,因此

要开发的代码必须是可扩展的,以便您可以播放和实验

如果愿意,可以尝试一下。

我们还希望看到一些结果,所以我们不能只是开发编译器,我们必须

还开发了一种解释器以查看实际运行情况。 但首先

我们必须仔细研究一些理论和定义:

介绍

编译器通常将源语言翻译成目标语言。 这俩

源语言和目标语言可以是任何东西:源语言可以是

高级编程语言,可能是虚拟的汇编代码语言

机器,甚至是自然语言,例如英语,荷兰语或斯瓦希里语。 最

(但不是全部)时间,目标语言将是比

源语言。

编译器读取源语言并编写目标语言。 它是

翻译者基本上。 人类语言及其作为交流手段的用法

很难,尤其是涉及演讲时。 为了简单起见,我们

既不处理语音识别也不处理语音的困难方面

本身作为音素,词素,双语-唇语-牙齿-

口渴,声音,无声和其他有趣的声音。

但是即使涉及编程语言,事情也相当复杂。

最简单的说,编程语言是一系列字符,其中

某些字符组合组成一个“令牌”。 令牌是原子的

编程语言的实体。 想想保留字和符号,例如

如括号,数学运算符,数字等。但是空格可以

也是一个重要的象征; 大多数编程语言都将空格视为

分隔更有意义的标记的字符,但是它们非常

在字符串文字中很重要,例如“ helloworld”与“ hello world”不同

尽管对于人类来说,根据我们的启发式方法和

迷人的模式匹配能力,尽管我们无法真正解释*如何*

我们做到了。

编译器比人类愚蠢得多。 需要固定的规则来确定

字符序列是否构成有效令牌。 这是

编译器第一部分的任务:

分词器

标记器(或“词法分析器”)尝试从序列中形成标记

字符。 看看“ x +++++ y”。 它可以代表

在语法上有效的标记序列[x] [++] [+] [++] [y]

Java,但这不是Java的标记器的工作原理(尝试一下)。

表示法[[...]'表示单个标记,由字符形成

序列 '...'。

Java令牌生成器将该字符序列视为一系列令牌,例如

这:[x] [++] [++] [+] [x]

为什么? 因为Java的标记器是“贪婪的”,即它总是扫描字符

从左到右的顺序,试图“切断”尽可能长的时间

有效令牌。 为什么? 因为它需要一个简单的规则,如何从

字符序列。 分词器对实际语言了解不多

需要编译; 它只知道有效的令牌,然后尝试

尽可能高效地产生一系列令牌。 所以人类用户

必须在某处放置空间以帮助令牌生成器:“ x ++ + ++ y”。 Java考虑

其他所有失败时,将空格分隔为令牌分隔符。

在其他情况下,令牌生成器可以正常工作而无需插入空格

到处都是:“ if(true)x = 1;”。 '('字符不能是令牌的一部分

[if(]或[if(t]或[if(tr]等),因为此类标记在Java中不存在。

这里最长的第一个令牌是[if],并且这里不需要空间。 的

当然,出于可读性的考虑,它可以帮助人们在此处和此处放置空间。

在C和C ++中,甚至需要括号的情况也更糟。

看一下这个C示例:


if (x) * (y=z);
对于y合适的指针值,这在C和C ++中是有意义的。 离开了

括号:


if x * (y=z);
这不再有意义:是否应该是一个(丑陋的)条件,例如:

“ x *(y = z)”后跟一个空语句,或者应该是条件“ x”

后跟一个(丑陋的)语句“ *(y = z);” 两种变体都没有多大意义

从语义上讲,但是编译器知道什么? 应该是

翻译这些字符序列,并产生另一种(机器代码)语言。

如果我写:“ <S-F6> <S-F7> fsggf *&^&^ *&kjgf(&”,您将不会理解我

因为以上*在词法上*没有意义,所以是分词器的主题

必须处理。 令牌生成器必须将字符序列切碎,然后

它必须从中形成令牌。

您可能从未意识到这一点,但其中很多括号,分号

大括号就可以帮助编译器的另一部分:

解析器

解析器期望由令牌生成器生成的令牌流。 例如,

Java tokenizer处理以下字符序列:“)if(x;)(y”

作为令牌的完全有效序列:[]] [if] [(] [x] [;] [)] [(] [y]

但这对解析器没有多大意义。 令牌的顺序可以是

对令牌生成器有效,*语法上*,令牌序列没有意义。

人类非常善于理解语法错误

句子; 我们每天都写/说和听。

如果我写:“我现在喝啤酒要喝!” 你最有可能了解我

尽管句子在语法上是错误的 你不会

当我写信时明白我的意思:“船屋现在很想得到那个”

当然是这样。 如果我不知道我在说什么

写道:“房子现在要装船了”。

房屋不破坏船只。 虽然这句话在语法上是正确的

是没有道理的。 以“语义”方式没有意义。

人类擅长“理解”语法错误的句子,但我们不是

当涉及到毫无意义的句子时,它有什么优点; 我们发现

他们充其量不过是幽默,但我们并不十分在意语法的正确性。

然后,我们需要正确的语义。 编译器也这样做:

语义分析器

编译器几乎无法处理语义:它们可以处理类型检查

就是这样。 大多数语义会转移到解释器或

虚拟机还是真实计算机。 编译后(翻译)

已经结束并完成了运行时环境(以任何形式)的处理

具有以下语义:检查是否除以零,检查是否

存在文件,如果可以打开套接字等。

编译器可以检查是否声明了每个使用的方法,如果每个

使用的成员变量已定义,它可以检查是否

用于定义在某处。 它还可以执行以下简单操作:


int x= "hello world";
这是一个完美的令牌流,从语法上讲一切都很好,但是令牌的类型

有两件事是不正确的:我们不能将字符串分配给int; 该程序

片段在语义上不正确。 但是在运行时系统可以做之前

首先,必须生成“任何东西”:

代码生成器

代码生成就像将字节发送到某个地方一样简单(机器代码

代),但它也可能很复杂; 看这个小例子:


double x= 1/42.0;
x= Math.sin(x)/Math.cos(x)+Math.log(x)-3.14159;
x= 0;
初始化真的有必要吗? 那复杂的表情怎么样?

显然不是因为最后x始终会被设置为0。

那么为什么要为所有这些生成代码? 也许用户想要调试此代码,所以

无论如何,所有代码都应生成。 也许用户想要优化的代码,所以

几乎根本不应该生成任何代码:应该进行简单的“ x = 0”初始化

足够。 代码生成器的工作是在

它所知道的上下文。 代码生成器对字符一无所知

或令牌序列。 分词器和解析器负责这一点。 解析器确实

需要时调用代码生成器,但解析器不知道是否

并非真正需要该代码。

通常,代码生成器由解析器调用; 他们知道需要什么代码

是否生成,是否冗余。 代码生成器应该找出

是否真的需要所有这些代码。

结束语

本文的第一部分介绍了以下几个阶段(或模块)

编译器。 严格来说,解释器或运行时系统不是一部分

编译器。 首先,字符流被转换为令牌流。

检查令牌流在语法上是否有意义。

解析器知道何时调用代码生成器。 代码生成器必须

生成有效的代码。 完成所有操作后,口译员便开始工作

要做的事情:执行用户最初使用字符流时要执行的操作。

实际上,相之间的分离可能会有点模糊,即

解析器更改令牌生成器的行为,代码生成器查询

解析器了解一些语义细节,令牌生成器再次与解析器对话

等。但是模块之间的区别有助于开发

新的编译器。

本文的这一部分没有任何有用的代码。 那留给

接下来的部分。 本文的第二部分介绍了分词器和

以下部分深入分析器,代码生成器和解释器。

我们的分词器使用正则表达式来切分字符序列

成令牌序列。

这一切的源代码故意不使用任何外部工具

标记器部分或解析器部分,仅用于教育目的。

有很多非常好的工具可用于生成令牌生成器和

为我们解析器,但这就是从头开始构建它自己。

现在我必须弄清楚那种语言会是什么样子

应该有能力 保持双手交叉;-)

下周在本文的第二部分见。

亲切的问候,

乔斯

From: https://bytes.com/topic/java/insights/649396-compilers-1-introduction

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值