当前位置: 首页 > 网络应用技术

写给小拜的开源编译器

时间:2023-03-05 23:26:59 网络应用技术

  我不知道你是否像我一样。当您看到“编译器”一词时,您会感到很高。同时,您的心中会有一些“害怕”的痕迹!

  我一直认为编译器非常复杂……非常复杂,不是我能理解的。当我想到学习编译器的知识时,我脑海中出现了500页厚书。

  直到我发现宝藏级开源项目Super-tiny compiler是一个仅约1,000行的迷你编译器,占代码金额的80%,实际代码仅为200行!麻雀很小,但五个内部器官已经完全实现,编译器要求的基本功能已完全实现。通过代码+注释+说明,让您开始使用开源项目。

  地址:https://github.com/jamiebuilds/the-super-tiny-编译器

  中文:https://github.com/521xueweihan/onefile/blob/main/src/javascript/the-tiny-compileer.js

  在下面我将从介绍编译器开始,将上述项目用作示例代码,说明编译过程更详细,并切断编译器的阈值以降低。如果您尚未接触到与此相关的知识编译器之前,本文允许您对编译器的作用和原则有初步的了解!

  您准备变得更强壮吗?让我们开始!

  简单地从这个概念中说:

  编译器是一个将“一种语言(通常是高级语言)”转换为“另一种语言(通常是低级语言)”的程序。

  对于现代程序员来说,我们最熟悉的JavaScript和Java是高端语言,它是促进我们的程序员编写,阅读,理解和维护的语言。低级别的语言是计算机可以直接解释和运行的语言。

  编译器也可以理解为这两种语言之间的“桥梁”。编译器的原因是因为计算机CPU执行数百万个小操作,因为这些操作太“小”,您一定不想手动写它们,因此,有二进制外观,并且还通过了解计算机代码来理解二进制代码。显然,二进制似乎不了解,编写二进制代码很麻烦。因此,CPU体系结构将二进制操作映射作为更简单的 - 阅读语言 - 启动语言。

  尽管汇编语言非常低,但可以转换为二进制代码。这种转换主要取决于“汇编”。由于汇编语言仍然非常低 - 对于追求高效率的程序员来说是难以忍受的,因此出现了更高级的语言。这也是大多数程序员使用的程序员。将其转换为机器操作,但比汇编语言更好地理解并且可以有效地使用。因此,我们需要的是理解这些复杂语言并正确转换为低级别的代码- 专家。

  我认为对初学者有一个一般的理解。因为接下来要分析的示例非常简单,但是它可以涵盖大多数情况,您将从最真实和直接的观点面对“大敌人” - 专家。

  在下面,我们以柔和的小补偿器为例,以使每个人简要理解编译器。

  不同编译器的不同阶段可能存在差异,但基本上它们与这三个主要组成部分密不可分:解析,转换和代码生成。

  实际上,此“迷你”编译器开源项目的目的是:

  以上解释了此开源项目的重要性,因此,如果您对编译器有浓厚的兴趣,我希望能学会到最后,那么它绝对是与大量阅读和研究密不可分的,但是如果您想对编译器的编译器在编译中,您有一些要知道的内容,请不要错过这篇文章!

  现在,我们必须进一步学习项目本身,并且需要提前学习一些背景。该项目主要将LISP语言编译成我们熟悉的JavaScript语言。

  那么为什么使用LISP语言呢?

  LISP是一个具有悠久历史的计算机编程语言家族,具有独特而完整的前缀符号。

  首先,LISP语言与我们熟悉的C语言和JavaScript语言大不相同。尽管其他语言也具有强大的编译器,但它们比LISP语言要复杂得多。LISP语言是一种超级简单的分析语法,很容易被翻译成其他语法,例如:

  因此,您应该知道我们在这里做什么?然后让我们了解如何“翻译”(汇编)!

  我们之前已经提到,大多数编译器主要在做三件事:

  在下面,我们将分解super微小的编译器的代码,然后逐行解释。

  让我们一起按照代码弄清楚以上三个阶段已完成了什么。

  分析通常分为两个阶段:词汇分析和语法分析

  例如,下面的语法:

  像这样删除令牌阵列:

  代码思维:

  因为我们需要解析字符串,所以我们需要一个肖像/光标来帮助我们确定当前分析的位置,因此会有一个变量,从0开始。我们的最终目的是获得一个令牌数组,因此我们首先初始化一个空数组。

  由于您想分析字符串,因此自然是必不可少的!这是分析我们当前字符串的一个段循环。

  如何在字符串中获得一个字符?答案是像数组一样获得颈托:

  在此处添加一个新的知识点!JavaScript中字符串类的实例是类数组。从下面的示例可以看出:

  也许您以前将使用单个字符来使用单个字符,因为它是字符串类型中的一种方法:

  两种方法都可以达到您想要的效果,但是存在差异。当不存在投标时,它将返回,并且它将返回(空字符串):

  通过光标的运动和字符串中字符的获取,我们可以逐步分析当前字符串。

  然后,也可以从这些方面考虑分析。以此为例,我们将遇到这些:左括号,字符串,空间,数字,右括号。对于不同类型,有必要使用不同的条件来单独判断:

  由于字符串和数字具有不同的含义,因此处理相对复杂。首先从数字开始,因为数字的长度未固定,因此要确保获得所有数字字符串,您必须通过。这个号码。

  为了更适合实际场景,这里支持字符串的操作,例如此表单的操作,然后分析内部字符串。随后进行调查:

  标记的最后类型是名称。这是字母序列而不是数字。这是我们语法中的函数名称:

  我们可以在上方获得一个阵列。下一步是构建抽象语法树(AST)可能看起来像这样:

  代码思维:

  同样,我们还需要一个光标/指针来帮助我们确定当前操作是谁,然后提前创建我们的AST树。他有一个名为:

  看一下我们之前获得的阵列:

  您会发现,对于这种嵌套的字符串,此数组非常“平坦”,无法显然表达嵌套的关系,我们的AST结构可以清楚地表达嵌套的关系。对于上述数组,我们需要穿越每个标记并找到出来,直到正确的括号结束为止,因此递归是最好的方法,因此我们创建了递归方法。此方法在我们的数组中返回一个节点。

  让我们实现我们的方法。我们希望此方法可以正确分析数组中的信息。首先,有必要区分不同类型:

  首先操作“值”,因为它不充当父节点,所以它是最简单的。我们已经了解到值可能是数字或字符串,只要值和类型与类型匹配:

  接下来,我们正在寻找调用表达式(callexpressions)。左括号的仪式可以获取表达式的名称,在遇到右括号之前,将树结构富集,直到右括号停止直到循环结束为止。从代码的角度来看,它更直观:

  编译器的下一个阶段是转换。您要做的是在获得AST后进行更改。它可以用相同的语言操作AST,或者可以将其转化为新语言。

  那么如何改变AST?

  您可能会注意到我们的AST中的元素看起来非常相似。这些元素具有类型属性,称为AST节点。这些节点包含多个属性,可用于描述AST的一些信息。

  对于转换而言,AST无非是通过新的,删除和替换属性操作节点,或者还可以添加节点和删除节点,甚至我们也可以根据其原始AST结构创建新的AST。

  由于我们的目标是一种新语言,因此我们将专注于创建一个全新的AST,以与这种特定语言合作。

  为了能够访问所有这些节点,我们需要遍历它们并使用深度遍历的方法。这应该像我们上述的AST遍历过程一样:

  如果我们直接操作此AST而不是创建单独的AST,我们很可能会在此处引入各种抽象。但是,仅访问树上的每个节点是我们要做和可以做的很多事情。

  (访问词)是因为这是一个代表对象结构中元素操作的模型。)

  因此,我们现在创建一个访客对象(访问者)。该对象有一种接受不同节点的方法:

  当我们穿越AST时,如果遇到匹配节点,我们可以调用该方法。

  通常,为了使这些方法变得更好,我们将将父节点作为参数传递。

  当然,对于遍历遍历的遍历,我们都知道,当遍历最深的自我主题时,我们需要“回去”,这就是我们所说的“退出”当前节点。您也可以理解:“输入”节点并上升“退出”节点。

  为了支持这一点,我们的“访客”的最终形式应该是这样:

  首先,我们定义一个接收AST和访问者的Traverser函数。需要根据每个节点的类型调用不同访问者的方法,因此我们定义了传递当前节点及其父节点的方法。从根节点开始,root节点没有父节点,因此您可以将其传递。

  由于两种类型可能包含子节点,因此需要对这些可能的子节点数组进行进一步处理,因此创建了一种呼叫方法来迭代。

  下一步是将先前的AST树进一步转换为JavaScript的AST树,因为我们期望的是:JavaScript:

  如果您不熟悉JS AST的语法分析,则可以使用在线工具网站来帮助您知道要将哪种AST树转变为更复杂的方案?

  我们创建了一个像以前的AST树一样创建新的AST树,还有一个节点:

  然后,让我们改善我们的对象。对于不同类型的节点,它可以定义其和谐(这是因为您只需要访问节点并处理该节点,因此不使用退出节点的方法:)::

  它会相对复杂,因为它可能包含嵌套内容。

  编译器的最后阶段是代码生成。在此阶段,有时与转换重叠,但是代码生成的最重要部分是基于AST输出代码。有几种不同的代码生成方式。一些编译器将重复使用以前生成的令牌,有些则将创建独立的代码表示以促进线性输出代码。但是我们仍然专注于以前生成的可用AST。

  根据前几个步骤,我们有了新的AST树:

  接下来,您将调用代码生成器递归调用以打印树的每个节点,最后输出一个字符串。

  生成代码后,我们得到了这样的JS字符串:这意味着我们的编译过程成功了!

  以上是将LISP写给JS编译器的整个过程。中文注释的完整代码地址:

  https://github.com/521xueweihan/onefile/blob/main/src/javascript/the-su-tiny-compileer.js

  那么,我今天将在哪里学到?尽管众所周知的Vue和React不同,但“返回的不同方式”是通过AST转换的前端框架。中间最重要的是转换AST。它非常“强大”并且广泛使用。更常用的情况:

  AST广泛用于编译器,IDE和代码优化压缩以及前端框架VUE,RECT等等。

  当然!阅读文章后,没有必要真正理解它。所有的学习过程都与实际实践密不可分。也许您会在练习过程中有不同的理解。实用方法非常简单:只需打开浏览器的“开发人员模式” - >输入控制台(控制台) - >复制/粘贴代码,您就可以直接运行到查看结果!

  以上是本文的所有内容。本文只能被认为是对编译器的外观的粗略研究。如果您有不同的意见,请在评论区域中留言互动并共同取得进展!

  原始:https://juejin.cn/post/709886950218767463