如何创建自己的编程语言和用于它的编译器[关闭]


427

我对编程很了解,并且遇到过各种语言,包括BASIC,FORTRAN,COBOL,LISP,LOGO,Java,C ++,C,MATLAB,Mathematica,Python,Ruby,Perl,JavaScript,Assembly等。我不明白人们是如何创建编程语言并为其设计编译器的。我也无法理解人们如何创建Windows,Mac,UNIX,DOS等操作系统。对我来说,另一件事是神秘的,人们如何创建像OpenGL,Op​​enCL,OpenCV,Cocoa,MFC等之类的库。我无法弄清的最后一件事是科学家如何设计微处理器的汇编语言和汇编器。我真的很想学习所有这些知识,而且我今年15岁。我一直想成为计算机科学家,例如Babbage,Turing,Shannon或Dennis Ritchie。


我已经读过Aho的Compiler Design和Tanenbaum的OS概念书,他们都只在高层次上讨论概念和代码。它们不涉及细节和细微差别,也不涉及如何设计编译器或操作系统。我需要一种具体的理解,以便我自己创建一个,而不仅仅是对线程,信号量,进程或解析的理解。我问我兄弟这一切。他是麻省理工学院EECS的SB学生,对如何在现实世界中实际创建所有这些东西一无所知。他所知道的只是对编译器设计和OS概念的理解,就像你们提到的(即线程,同步,并发,内存管理,词法分析,中间代码生成等)一样。


如果你是在Unix / Linux,你可以得到关于专用工具的信息:lexyaccbison
mouviciel 2011年

我的第一个建议是读Aho的《龙书》。amazon.com/Compilers-Principles-Techniques-Alfred-Aho/dp/…–
朱利安

1
也许不是太大的帮助,但我建议穿通sites.google.com/site/steveyegge2/blog-rants(史蒂夫·耶格的博客),以及steve-yegge.blogspot.com/(史蒂夫·耶格的其他博客)。
KK。

3
尽可能多地学习编程语言。这样,您将从他们的概念和错误中学习。当您可以站在巨人的肩膀上时,为什么还要对矮人感到满意?
2011年

1
提示:解释器比编译器容易;它只是一个类,它根据逐行读取的输入文本来“执行某些操作”。另一个提示:将其绑定到反射,您可以使用脚本控制任意对象。
Dave Cousineau'8

Answers:


407

基本上,您的问题是“如何设计和实现计算机芯片,指令集,操作系统,语言,库和应用程序?” 这是一个价值数十亿美元的全球性行业,拥有数百万名员工,其中许多是专家。您可能需要更多地关注您的问题。

就是说,我可以采取以下行动:

我不明白人们是如何创建编程语言并为其设计编译器的。

这让我感到惊讶,但是很多人确实认为编程语言是神奇的。当我在聚会或其他场合与人会面时,如果他们问我要做什么,我告诉他们我设计编程语言并实现编译器和工具,令人惊讶的是,人们(专业程序员,介意您)说的次数“哇,我没想过,但是,是的,有人必须设计那些东西。” 就像他们认为语言刚刚兴起一样,已经形成了周围的工具基础架构。

他们不只是出现。语言的设计与其他任何产品一样:通过在竞争可能性之间进行一系列权衡来进行。编译器和工具的构建方式与其他任何专业软件产品一样:通过分解问题,一次编写一行代码,然后测试所生成程序的技巧来进行。

语言设计是一个巨大的话题。如果您对设计语言感兴趣,那么一个不错的起点就是考虑一下您已经知道的某种语言的不足之处。设计决策通常是由考虑其他产品的设计缺陷引起的。

或者,考虑您感兴趣的域,然后设计一种特定于域的语言(DSL),该语言指定该域中问题的解决方案。您提到了LOGO;这是“线条图”域的DSL的一个很好的例子。正则表达式是用于“在字符串中查找模式”域的DSL。C#/ VB中的LINQ是用于“过滤,联接,排序和项目数据”域的DSL。HTML是用于“描述页面上文本的布局”域的DSL,依此类推。有许多领域适合基于语言的解决方案。我最喜欢的一个是Inform7,它是用于“基于文本的冒险游戏”域的DSL。它可能是我见过的最高级别的严肃编程语言。

勾勒出您想要的语言外观后,请尝试准确地写下确定合法和非法程序的规则。通常,您需要在三个级别上执行此操作:

  1. 词汇:语言中单词的规则是什么,哪些字符是合法的,数字是什么样的,等等。
  2. 句法:语言单词如何组合成更大的单位?在C#中,较大的单元是诸如表达式,语句,方法,类之类的东西。
  3. 语义:给定语法上合法的程序,您如何确定该程序的作用

尽可能精确地写下这些规则。如果您做得很好,则可以将其用作编写编译器或解释器的基础。看一下C#规范或ECMAScript规范,了解我的意思;它们充满了非常精确的规则,这些规则描述了什么构成法律程序以及如何弄清法律程序的作用。

开始编写编译器的最佳方法之一是编写高级语言到高级语言的编译器。编写一个使用您的语言的字符串并使用C#或JavaScript或您碰巧知道的任何语言吐出字符串的编译器;让该语言的编译器负责将其转换为可运行代码的繁重工作。

我写了一篇有关C#,VB,VBScript,JavaScript和其他语言和工具的设计的博客。如果您对此主题感兴趣,请查看。http://blogs.msdn.com/ericlippert(历史)和http://ericlippert.com(当前)

特别是,您可能会发现这篇文章有趣。在这里,我列出了C#编译器在语义分析过程中为您执行的大多数任务。如您所见,有很多步骤。我们将大的分析问题分解为一系列可以单独解决的问题。

http://blogs.msdn.com/b/ericlippert/archive/2010/02/04/how-many-passes.aspx

最后,如果您正在寻找一份年纪较大的工作,那么可以考虑以微软的大学实习生身份进入开发人员部门。这就是我今天结束工作的方式!


您是否写过CLR可以自动完成编译器优化的程度?

6
@Thorbjørn:让我们弄清楚术语。“编译器”是将一种编程语言转换为另一种编程语言的任何设备。拥有一个将C#转换为IL的C#编译器和一个将IL转换为机器代码的IL编译器(“抖动”)的好处之一是,您可以将C#编译器编写为IL(很简单!),并且将处理器特定的优化置于抖动中。并不是说编译器优化没有“完成”,而是jit编译器团队为我们完成了优化。见blogs.msdn.com/b/ericlippert/archive/2009/06/11/...
埃里克利珀

6
@ Cyclotis04:Inform6编译为Z代码,这是基于字节码的虚拟机的一个非常著名的早期示例。这就是1980年代所有Infocom游戏都可以既大于内存又可以移植到多种架构的方式。将游戏编译为Z代码,然后为多台计算机实现了带有代码存储分页的Z代码解释器。当然,如今,如果需要,您可以在手表上运行zcode解释器,但是那是高科技时代。有关详细信息,请参见en.wikipedia.org/wiki/Z-machine
埃里克·利珀特

@EricLippert编译器不是设备,设备是包含硬件的东西。我们可以说是一个预定义的程序,该程序具有一组将输入数据转换为机器代码的规则
dharam,2014年

2
@dhams:设备是为特定目的而制造的任何东西。我曾经编写的每个编译器都在专门用于允许编译器存在的硬件上运行。
埃里克·利珀特

127

您可能会发现Jack Crenshaw的Lets Build a Compiler有趣的介绍了如何编写编译器和汇编语言。

作者保持非常简单,专注于构建实际功能。


2
Crenshaw简介的有趣之处在于它就结束了(破坏者:它是不完整的),恰好是在您碰到会让您意识到的问题的时候,嘿,我真的应该在开始实施它之前就已经完全设计了我的语言。然后您说,嘿,如果我必须编写完整的语言规范,为什么不以正式的符号来表示它,然后我就可以将其输入工具以生成解析器了?然后您像其他所有人一样进行操作。
kindall 2011年

3
@kindall,您需要手工完成,以便意识到有理由使用这些工具。

72

“我真的很想学习这些东西”。如果您长期认真:

  • 上大学,专攻软件工程。取得所有可以获得的编译器类。那些上课的人比你受过更好的教育和更有经验;最好利用他们的专家观点以您从阅读代码中不会得到的方式向您提供信息。

  • 坚持从高中开始的数学课程,并继续在大学学习所有4年。专注于非标准数学:逻辑,群论,元数学。这将迫使您进行抽象思考。它使您能够阅读有关编译的高级理论论文,并理解为什么这些理论有趣且有用。如果您永远想落后于最新技术,则可以忽略那些高级理论。

  • 收集/阅读标准的编译器文本:Aho / Ullman等。它们包含社区普遍认为的基本内容。您可能不会使用这些书中的所有内容,但您应该知道它的存在,并且应该知道为什么不使用它。我以为Muchnick很棒,但这是针对高级主题的。

  • 构建一个编译器。现在开始建立一个烂的。这将教您一些问题。建立第二个。重复。这种经验与您的书籍学习建立了巨大的协同作用。

  • 一个真正好的起点是学习BNF(Backus Naur格式),解析器和解析器生成器。BNF在编译器领域得到了有效的普遍使用,如果您不了解BNF,您将无法与之交谈。

如果您想对编译进行全面的介绍,以及BNF的直接价值(不仅仅是文档),而是作为一种可工具处理的元语言,请参阅本教程(不是我的文章),该教程基于 Windows 构建“元”编译器(构建编译器的编译器)。1964年的论文(是的,您没看错)[Val Schorre撰写的“ META II,一种面向语法的编译器编写语言”。(http://doi.acm.org/10.1145/800257.808896)]该IMHO是有史以来撰写的最好的comp-sci论文之一:它教您以10页的篇幅构建编译器。我最初是从本文中学到的。

我上面写的内容来自个人经验,我认为它对我很有帮助。YMMV,但恕我直言,不是很多。


54
-1以上都不是必需的。
尼尔·巴特沃思

77
@nbt以上都不是必需的。但是以上所有这些都有帮助。真的很多
Konrad Rudolph

1
我特别不同意“学习数学以进行抽象思考!” 建议。即使您认为“学习抽象思维”对于创建自己的编程语言和编译器特别有帮助(我不知道,我发现这样做比通过这些回旋,难以置信的间接路线来学习要有用得多) ,数学不是唯一具有抽象思想的领域!(我是一个数学家,所以我不否认使用一般的数学,只是数学在这种情况下的适用性...)
grautur 2011年

26
如果您想阅读有关编译器理论的高级技术论文,则最好在数学上胜任。您可以决定忽略该文献,因此您的理论以及相应的编译器将因此变得更糟。这里的反对者都指出,您可以在无需大量正规教育的情况下构建编译器,我同意。他们似乎暗示您可以在没有它的情况下构建非常好的编译器。我不想打赌。
伊拉·巴克斯特

7
CS是一门真正对语言设计和实现有用的学科。当然不是强制性的,但是已有数十年的研究可以并且应该加以利用,并且根本没有理由重复其他错误。
Donal Fellows

46

这是您可以遵循的在线书籍/课程,名为《计算系统的要素:从第一原理构建现代计算机》

使用模拟器,您实际上可以从头开始构建完整的计算机系统。尽管许多评论者都说您的问题太过广泛,但本书实际上在保持可管理性的同时回答了它。完成后,您将使用高级语言(您设计的)编写游戏,该游戏使用您自己的操作系统功能,并由编译器编译为您设计的VM语言。由VM转换器翻译成汇编语言(由您设计),由汇编程序汇编成机器代码(由您设计),该汇编程序在计算机系统上运行,并通过使用布尔逻辑和一种简单的硬件描述语言。

这些章节:

  1. 课程大纲
  2. 布尔逻辑
  3. 组合芯片
  4. 顺序筹码
  5. 机器语言
  6. 计算机架构
  7. 组装工
  8. 虚拟机I:算术
  9. 虚拟机II:控制
  10. 程式语言
  11. 编译器I:语法分析
  12. 编译器II:代码生成
  13. 操作系统
  14. 项目清单

更有趣


感谢您的修改,不知名的人。我尝试了几次,但无法将自己的思想集中在描述上……但不想不想提及这本书。该书现已在学习计划链接上在线发布:www1.idc.ac.il/tecs/plan.html。它在网上的价格也很合理。享受大家。
Joe Internet,

我打算自己提出一个建议...懒惰的人,请查看10分钟的介绍:从NAND到俄罗斯方块的12个步骤@ youtube.com/watch?v=JtXvUoPx4Qs
Richard Anthony Hein

46

退后一步。编译器只是一个将一种语言的文档翻译成另一种语言的文档的程序。两种语言都应该定义明确且特定。

语言不必是编程语言。它们可以是任何可以写下规则的语言。您可能已经看过Google Translate;那是一个编译器,因为它可以将一种语言(例如德语)翻译成另一种语言(也许是日语)。

编译器的另一个示例是HTML呈现引擎。它的输入是一个HTML文件,输出是一系列在屏幕上绘制像素的指令。

当大多数人谈论编译器时,他们通常是指将高级编程语言(例如Java,C,Prolog)转换为低级编程语言(汇编或机器代码)的程序。这可能令人生畏。但是,如果您以通才的观点认为编译器是将一种语言翻译成另一种语言的程序,那还不错。

您可以编写一个程序来反转字符串中的每个单词吗?例如:

When the cat's away, the mice will play.

变成

nehW eht s'tac yawa, eht ecim lliw yalp.

编写该程序并不难,但是您需要考虑一些事项:

  • 什么是“单词”?您可以定义由哪个字符组成一个单词吗?
  • 单词在哪里开始和结束?
  • 单词是仅由一个空格分隔,还是可以有更多或更少?
  • 标点符号也需要颠倒吗?
  • 单词里面的标点符号呢?
  • 大写字母会怎样?

这些问题的答案有助于明确定义语言。现在继续编写程序。恭喜,您已经编写了一个编译器。

怎么样:您能否编写一个程序,该程序需要一系列绘图指令并输出PNG(或JPEG)文件?也许是这样的:

image 100 100
background black
color red
line 20 55 93 105
color green
box 0 0 99 99

同样,您需要做一些思考来定义语言:

  • 什么是原始指令?
  • “ line”一词之后会有什么?“颜色”之后会怎样?对于“背景”,“框”等也是如此。
  • 什么是数字?
  • 是否允许输入文件为空?
  • 可以大写单词吗?
  • 是否允许使用负数?
  • 如果不给出“ image”指令,会发生什么?
  • 不指定颜色可以吗?

当然,还有更多问题要回答,但是如果您可以确定问题,那么您已经定义了一种语言。猜猜您编写的用于翻译的程序是编译器。

您会看到,编写编译器并不难。您在Java或C中使用的编译器只是这两个示例的较大版本。所以去吧!定义一种简单的语言并编写程序以使该语言发挥作用。迟早您将要扩展语言。例如,您可能要添加变量或算术表达式。您的编译器将变得更加复杂,但是您将了解它的每一个细节,因为您自己编写了它。语言和编译器就是这样产生的


7
myFirstCompiler =(str)->(“” +(str ||“”))。split('')。reverse()。join(''); jsfiddle.net/L7qSr
拉里·

21

如果您对编译器设计感兴趣,请查阅《Dragon Book》(官方标题:Compilers:Principles,Techniques和Tools)。它被广泛认为是关于该主题的经典书籍。


4
请注意,您可能需要更多实际经验才能充分利用本书。很好的参考。

13
-1只有没有看过书的人才能认为龙书有什么用。特别是它没有解决这个问题。
尼尔·巴特沃思

33
龙书?热情的15岁?我希望他再保持一段时间的热情。
David Thornley

1
一个更易于访问的替代方法:“ Programming Language Pragmatics” 3e
willjcroz 2011年

@DavidThornley不要完全把他排除在外(是的,我知道这是一个非常古老的职位)。我从15岁开始研究语言的工作方式,并专门研究虚拟机。现在我16岁,经过几个月的研究,编写和重写,我对工作的解释器和编译器感到满意。
戴维


10

不要相信编译器或操作系统有什么魔力:没有。还记得您编写的用于计算字符串中所有元音或将数组中的数字相加的程序吗?编译器在概念上没有什么不同。它更大了。

每个程序分为三个阶段:

  1. 读一些东西
  2. 处理这些东西:将输入数据转换为输出数据
  3. 写一些其他的东西–输出数据

考虑一下:编译器的输入是什么?源文件中的字符串。

编译器输出什么?表示目标计算机的机器指令的字节字符串。

那么,编译器的“处理”阶段是什么?该阶段做什么?

如果您认为编译器-像任何其他程序- 包括这三个阶段,你就会有一个编译器是如何构造一个好主意。


3
正如尼尔所说,是真实的但没有用。基本的编译器方面(例如递归语法和符号表)在直观上并不明显。
梅森惠勒

1
@Mason Wheeler:我认为任何渴望写出编译器(并设计目标语言?)的人都最有可能认为递归语法符号表是非常基本的概念。
FumbleFingers 2011年

8

我不是专家,但这是我的刺刀:

您似乎并没有询问编写编译器,而只是询问汇编器。这不是魔术。

从SO窃取别人的答案(https://stackoverflow.com/questions/3826692/how-do-i-translate-assembly-to-binary),汇编如下所示:

label:  LDA #$00
        JMP label

然后,通过汇编器运行它,并将其变成如下所示:

$A9 $00
$4C $10 $00

只是全部压缩了,像这样:

$A9 $00 $4C $10 $00

真的不是魔术。

您不能在记事本中编写该代码,因为记事本使用ASCII(不是十六进制)。您将使用十六进制编辑器,或者只是以编程方式将字节写出。您将十六进制写出到文件中,将其命名为“ a.exe”或“ a.out”,然后告诉操作系统运行它。

当然,现代CPU和操作系统确实非常复杂,但这是基本思想。

如果要编写新的编译器,请按以下步骤进行:

1)在pyparsing(或任何其他良好的解析框架)中使用类似于计算器示例的方式编写解释性语言。这将使您快速掌握解析的基础知识。

2)写一个翻译器。将您的语言翻译成Javascript。现在,您的语言将在浏览器中运行。

3)写一个翻译器到较低层次的东西,例如LLVM,C或Assembly。

您可以在这里停止,这是一个编译器。它不是一个优化的编译器,但这不是问题。您可能还需要考虑编写链接程序和汇编程序,但是您真的要这样做吗?

4)(疯狂)编写一个优化器。大型团队为此工作了数十年。

4)(Sane)参与现有社区。GCC,LLVM,PyPy,负责任何口译员的核心团队。


8

其他几个人给出了很好的答案。我将再添加一些建议。首先,关于您要尝试做的一本好书是Appel的Modern Compiler Implementation文本(请选择CJavaStandard ML)。本书为您提供了一种简单的编译器的完整实现,该编译器使用了简单的语言Tiger到可以在仿真器中运行的MIPS程序集,以及最少的运行时支持库。对于使编译语言能够正常工作所需的所有步骤,这是一本相当不错的书1

Appel将带您逐步了解如何编译预先设计的语言,但不会花太多时间在各种语言功能的含义上,也不会花时间就设计自己的语言的相对优点进行思考。在这方面,编程语言:概念和构造是不错的。《计算机编程的概念,技术和模型》也是一本深入思考语言设计的好书,尽管它是在单一语言(Oz)的背景下进行的。

最后,我提到Appel用C,Java和Standard ML编写了他的文章-如果您对编译器的构造和编程语言很认真,我建议学习ML并使用该版本的Appel。ML系列语言的强类型系统主要是功能性的-与许多其他语言不同的功能,因此,如果您还不知道功能性语言,则学习它们会磨练您的语言水平。而且,它们的模式匹配和功能思维方式非常适合您在编译器中经常需要进行的操作,因此,以ML为基础的语言编写的编译器通常比用C编写的编译器更短,更容易理解, Java或类似语言。 哈珀的书有关Standard ML的入门指南非常不错;通过这一工作,您应该可以准备学习Appel的Standard ML编译器实施手册。如果您学习Standard ML,那么也很容易选择OCaml进行后续工作;IMO,它为工作的程序员提供了更好的工具(与周围的OS环境更干净地集成,易于生成可执行程序,并且具有一些出色的编译器构建工具,例如ulex和Menhir)。


1作为长期参考,我更喜欢《龙书》,因为它有更多关于我可能要引用的内容的详细信息,例如解析器算法的内部工作原理,并且涵盖了各种方法,但是Appel的书非常好第一遍。基本上,Appel会教您一种通过编译器进行整体处理的方法,并指导您完成编译。《龙书》更详细地介绍了不同的设计替代方案,但提供的指导却很少,不足以使工作正常进行。


编辑:用Sethi替换不正确的Aho参考,提及CTMCP。


gh,我的大学口译课上有程序语言基础。太可笑了。我什至个人喜欢该方案并且不介意语法,这是作者对概念的拙劣解释,对我而言是毁了它。
格雷格·吉达

我喜欢Appel的续篇进行编译,但是我确实发现他的书假设了很多先验知识。
乔恩·哈罗普

6

我必须为大学班级创建一个编译器。

进行此操作的基础知识并不像您想象的那么复杂。第一步是创建语法。想一想英语的语法。以相同的方式,您可以解析具有主语和谓语的句子。有关更多内容,请阅读有关上下文无关文法

掌握语法(语言规则)后,编写编译器就像遵循这些规则一样简单。编译器通常会翻译成机器代码,但是除非您想学习x86,否则建议您考虑使用MIPS或自己制作虚拟机。

编译器通常有两个部分,一个扫描器和一个解析器。基本上,扫描程序会读取代码并将其分离为令牌。解析器查看这些令牌的结构。然后,编译器会按照一些相当简单的规则进行操作,以将其转换为所需的任何代码(汇编,字节码等中间代码等)。如果将其分解成越来越小的块,那么最终一点都不令人生畏。

祝好运!


8
概念上简单吗?是。其实很简单?号
Neil Butterworth

7
嗯 扫描/解析后,编译器需要进行类型检查/推断,优化,寄存器分配等。这些步骤非常简单。(使用解释的代码时,只需将这些部分推迟到运行时阶段即可。)
Macke

我不赞成:编译器有两个基本部分,其中一个是构建程序的抽象描述(通常分为扫描和解析),另一个是在某些地方再次编写该抽象描述的版本。其他形式(例如,机器代码)。(附带说明:优化的编译器通常会在写出抽象描述之前尝试对其进行改进,但这只是一种改进。)
Donal Fellows

6

Petzold的《代码》一书很好地介绍了非技术人员和技术人员,从最初的原理开始。它具有很高的可读性,范围广泛,不会陷入太多的泥潭。

现在,我已经写了这篇文章,我将不得不重新阅读它。



5

这个线程有很好的答案,但是我只是想添加我的,因为我也曾经有过同样的问题。(另外,我想指出的是,Joe-Internet推荐的书是很好的参考资料。)

首先是计算机如何工作的问题?这是这样的:输入->计算->输出。

首先考虑“计算”部分。稍后我们将介绍输​​入和输出的工作方式。

计算机本质上由处理器(或CPU)和一些内存(或RAM)组成。内存是位置的集合,每个位置可以存储有限数量的位,每个这样的存储位置本身都可以由数字引用,这称为存储位置的地址。处理器是可以获取数据的小工具。从内存中,根据数据执行一些操作,然后将一些数据写回到内存中。处理器从内存中读取数据后如何确定要读取的内容和要做什么?

为了回答这个问题,我们需要了解处理器的结构。以下是一个相当简单的视图。处理器本质上由两部分组成。一个是一组内置于处理器中的内存位置,用作处理器的工作内存。这些被称为“寄存器”。第二个是一堆用于使用寄存器中的数据执行某些操作的电子机器。有两个特殊的寄存器,分别称为“程序计数器”或“ pc”和“指令寄存器”或“ ir”。处理器将内存分为三部分。第一部分是“程序存储器”,用于存储正在执行的计算机程序。第二个是“数据存储器”。第三种用于某些特殊目的,我们将在后面讨论。程序计数器包含要从程序存储器中读取的下一条指令的位置。指令计数器包含一个数字,表示正在执行的当前操作。处理器可以执行的每个操作都由称为该操作的操作码的数字来引用。计算机的基本工作原理是将程序计数器引用的存储位置读入指令寄存器(并递增程序计数器,使其指向下一条指令的存储位置)。接下来,它读取指令寄存器并执行所需的操作。例如,指令可能是将特定的存储器位置读取到寄存器中,或写入某些寄存器或使用两个寄存器的值执行某些操作,然后将输出写入第三个寄存器。指令计数器包含一个数字,表示正在执行的当前操作。处理器可以执行的每个操作都由称为该操作的操作码的数字来引用。计算机的基本工作原理是将程序计数器引用的存储位置读入指令寄存器(并递增程序计数器,使其指向下一条指令的存储位置)。接下来,它读取指令寄存器并执行所需的操作。例如,指令可能是将特定的存储器位置读取到寄存器中,或写入某些寄存器或使用两个寄存器的值执行某些操作,然后将输出写入第三个寄存器。指令计数器包含一个数字,表示正在执行的当前操作。处理器可以执行的每个操作都由称为该操作的操作码的数字来引用。计算机的基本工作原理是将程序计数器引用的存储位置读入指令寄存器(并递增程序计数器,使其指向下一条指令的存储位置)。接下来,它读取指令寄存器并执行所需的操作。例如,指令可能是将特定的存储器位置读取到寄存器中,或写入某些寄存器或使用两个寄存器的值执行某些操作,然后将输出写入第三个寄存器。处理器可以执行的每个操作都由称为该操作的操作码的数字来引用。计算机的基本工作原理是将程序计数器引用的存储位置读入指令寄存器(并递增程序计数器,使其指向下一条指令的存储位置)。接下来,它读取指令寄存器并执行所需的操作。例如,指令可能是将特定的存储器位置读取到寄存器中,或写入某些寄存器或使用两个寄存器的值执行某些操作,然后将输出写入第三个寄存器。处理器可以执行的每个操作都由称为该操作的操作码的数字来引用。计算机的基本工作原理是将程序计数器引用的存储位置读入指令寄存器(并递增程序计数器,使其指向下一条指令的存储位置)。接下来,它读取指令寄存器并执行所需的操作。例如,指令可能是将特定的存储器位置读取到寄存器中,或写入某些寄存器或使用两个寄存器的值执行某些操作,然后将输出写入第三个寄存器。计算机的基本工作原理是将程序计数器引用的存储位置读入指令寄存器(并递增程序计数器,使其指向下一条指令的存储位置)。接下来,它读取指令寄存器并执行所需的操作。例如,指令可能是将特定的存储器位置读取到寄存器中,或写入某些寄存器或使用两个寄存器的值执行某些操作,然后将输出写入第三个寄存器。计算机的基本工作原理是将程序计数器引用的存储位置读入指令寄存器(并递增程序计数器,使其指向下一条指令的存储位置)。接下来,它读取指令寄存器并执行所需的操作。例如,指令可能是将特定的存储器位置读取到寄存器中,或写入某些寄存器或使用两个寄存器的值执行某些操作,然后将输出写入第三个寄存器。

现在,计算机如何执行输入/输出?我将提供一个非常简化的答案。参见http://en.wikipedia.org/wiki/Input/outputhttp://en.wikipedia.org/wiki/Interrupt。更多。它使用两件事,即内存的第三部分和称为中断的东西。连接到计算机的每个设备都必须能够与处理器交换数据。它使用前面提到的内存的第三部分来实现。处理器为每个设备分配一个内存片,并且设备和处理器通过该内存片进行通信。但是,处理器如何知道哪个位置指的是什么设备,以及设备何时需要交换数据?这就是中断的来源。中断本质上是向处理器发出的信号,它暂停当前的内容并将其所有寄存器保存到已知位置,然后开始执行其他操作。中断很多,每个中断都有一个唯一的编号。对于每个中断,都有一个与之关联的特殊程序。发生中断时 处理器执行与该中断相对应的程序。现在,根据BIOS以及硬件设备与计算机主板的连接方式,每个设备都会获得一个唯一的中断和一片内存。在BIOS的帮助下启动操作系统时,将确定每个设备的中断和内存位置,并为中断设置特殊程序以正确处理设备。因此,当设备需要一些数据或想要发送一些数据时,它将发出中断信号。处理器暂停正在执行的操作,处理中断,然后返回到正在执行的操作。中断有很多种,例如硬盘驱动器,键盘等。一个重要的中断是系统计时器,它会定期调用中断。还有一些可以触发中断的操作码,称为软件中断。

现在我们几乎可以了解操作系统的工作原理。启动时,操作系统会设置计时器中断,以便它以固定的时间间隔向操作系统提供控制。它还会设置其他中断来处理其他设备等。现在,当计算机运行一堆程序时,发生定时器中断,操作系统将获得控制权并执行重要任务,例如进程管理,内存管理等。操作系统通常还提供程序访问硬件设备的一种抽象方式,而不是让它们直接访问设备。当程序要访问设备时,它会调用os提供的一些代码,然后与该设备通信。这些涉及很多理论,涉及并发,线程,锁,内存管理等。

现在,理论上可以使用操作码直接编写程序。这就是所谓的机器代码。这显然是非常痛苦的。现在,处理器的汇编语言不过是这些操作码的助记符,这使得编写程序变得更加容易。简单的汇编程序是一个程序,该程序接受用汇编语言编写的程序,并用适当的操作码替换助记符。

如何设计一种处理器和汇编语言。要知道您必须阅读一些有关计算机体系结构的书。(请参阅joe-internet引用的书的第1-7章)。这涉及到学习布尔代数,如何构建简单的组合电路以进行加法,乘法等,如何构建存储器和顺序电路,如何构建微处理器等。

现在,如何编写计算机语言。首先可以用机器代码编写一个简单的汇编器。然后使用该汇编器为C的简单子集编写编译器。然后使用C的子集编写更完整的C版本。最后使用C编写更复杂的语言,例如python或C ++。当然,要编写一种语言,您必须首先设计它(与设计处理器一样)。再看一些有关这方面的教科书。

以及如何编写操作系统。首先,您以x86之类的平台为目标。然后,您确定它是如何启动的以及何时调用您的操作系统。典型的个人计算机以这种方式启动。它启动,BIOS执行一些测试。然后BIOS读取HDD的第一个扇区并将内容加载到内存中的特定位置。然后,它设置cpu以开始执行此加载的数据。这就是您被调用的点。此时,典型的操作系统会加载其自身的其余内存。然后,它初始化设备并设置其他内容,最后用登录屏幕向您打招呼。

因此,要编写操作系统,您必须编写“ boot-loader”。然后,您必须编写代码来处理中断和设备。然后,您必须编写用于过程管理,设备管理等的所有代码。然后,您必须编写一个api,该API允许在您的操作系统中运行的程序访问设备和其他资源。最后,您必须编写代码以从磁盘读取程序,将其设置为进程并开始执行。

当然,我的答案是公开简化的,可能几乎没有实际用途。为了辩护,我现在是理论上的研究生,所以我忘记了很多这些东西。但是您可以在Google上搜索很多这些东西并查找更多信息。


4

我记得自己在编程生涯中处于与您相似的困惑状态的那一点:我已经读了很多有关该理论的文章,​​如《龙》,《老虎》(红色),但还没有太多提示如何将所有内容放在一起。

把它联系在一起的是找到一个具体的项目要做(然后发现我只需要所有理论的一小部分)。

Java VM为我提供了一个很好的起点:从概念上讲它是一个“处理器”,但是它是从实际CPU的混乱细节中高度抽象出来的。它也提供了学习过程中一个重要且经常被忽略的部分:将事物分解再重新组合在一起(就像以前的孩子们以前用收音机做的事情一样)。

尝试使用反编译器和Java的Hello,World类。阅读JVM规范并尝试了解发生了什么。这将使您对编译器的工作有扎实的了解。

然后尝试创建 Hello,World类的代码。(实际上,您正在为一种高度专业化的语言创建特定于应用程序的编译器,在其中只能说Hello,World。)

尝试编写能够以Hello语言,World用其他语言编写的代码,并输出相同的类。做到这一点,以便您可以将字符串从“ Hello,World”更改为其他名称。

现在,尝试编译(在Java中)一个计算某种算术表达式的类,例如“ 2 *(3 + 4)”。将此类分开,编写一个可以重新组合在一起的“玩具编译器”。


3

1)华盛顿大学的精彩视频讲座:

CSE P 501编译器构建-2009年秋季,www.cs.washington.edu / education / courses / csep501 / 09au / lectures / video.html *

2)SICP http://groups.csail.mit.edu/mac/classes/6.001/abelson-sussman-lectures/ 和这本书同名。实际上,这对于任何软件工程师都是强制性的。

3)另外,关于函数式编程,Haskell,lambda演算,语义(包括名词性)和函数式语言的编译器实现。如果您已经知道Haskell,则可以从2005-SS-FP.V10.2005-05-24.HDV开始。 Uxx视频就是答案。请先关注Vxx视频。

http://video.s-inf.de/#FP.2005-SS-Giesl.(COt).HD_Videoaufzeichnung

(视频使用英语,其他课程则使用德语。)

  • 新用户最多只能发布两个超链接。

3

ANTLR是一个很好的起点。这是一个语言生成框架,类似于Lex和Yacc。有一个名为ANTLRWorks的GUI 可以简化该过程。

在.NET世界中,可以使用动态语言运行时来在.NET世界中生成代码。我已经编写了一种称为Zentrum的表达语言,该语言使用DLR生成代码。它将向您展示如何解析和执行静态和动态类型的表达式。


2

为了简单地介绍编译器的工作方式以及如何创建自己的编程语言,我推荐一本新书http://createyourproglang.com,该书更多地侧重于语言设计理论,而无需了解OS / CPU内部结构,即词法分析器,解析器。 ,口译员等。

它使用与创建最近流行的Coffee ScriptFancy编程语言相同的工具。


2

如果您说的都是真的,那么您就拥有一个有前途的研究人员的身份,并且只有一种方式就可以获得具体的理解:学习。我并不是说:“ 这个天才写的所有这些高级计算机科学书籍(特别是这些)!”;我的意思是:您必须与高水平的人在一起,才能成为像Charles Babbage,Alan Turing,Claude Shannon或Dennis Ritchie这样的计算机科学家。我不鄙视自学成才的人(我是其中之一),但是外面没有多少人喜欢你。我认真建议符号系统方案(SSP)斯坦福大学。正如他们的网站所说:

斯坦福大学的符号系统程序(SSP)专注于计算机和思想:使用符号表示信息的人工和自然系统。SSP将对人机关系的不同方面感兴趣的学生和教职人员召集在一起,包括...

  • 认知科学:研究人类智力,自然语言和大脑作为计算过程;
  • 人工智能:赋予计算机类人的行为和理解能力;
  • 人机交互:设计与人类用户良好配合的计算机软件和界面。

2

我将建议一些超出范围的内容:学习Python(或者也许是Ruby,但是我在Python方面有很多经验,所以我将在后面讨论)。不仅涉足其中,而且真正深入地了解它。

我建议这样做的原因有几个:

  1. Python是一种设计精良的语言。虽然它有一些疣,但它的恕我直言比许多其他语言要少。如果您是一位崭露头角的语言设计师,那么最好让自己接触尽可能多的优秀语言。

  2. Python的标准实现(CPython)是开源的并且有据可查,因此可以更轻松地理解该语言的幕后工作方式。

  3. Python被编译成一个简单的字节码,比汇编代码更易于理解,并且在运行Python的所有平台上都相同。因此,您将了解编译(因为Python确实将源代码编译为字节码)和解释(因为此字节码在Python虚拟机中被解释)。

  4. Python有许多建议的新功能,已在编号的PEP(Python增强建议)中记录。阅读PEP有趣,可以了解语言设计人员在选择功能实现方式之前如何考虑实现功能。(在这方面,仍在考虑中的PEP特别有趣。)

  5. Python具有来自各种编程范例的多种功能,因此您将学习解决问题的各种方法,并考虑使用您自己的语言来考虑更广泛的工具。

  6. Python使得通过装饰器,元类,导入钩子等以各种方式扩展语言变得非常容易,因此您可以在不实际离开语言的情况下使用新的语言功能。(顺便说一句:代码块是Ruby中的一流对象,因此您实际上可以编写新的控制结构,例如循环!我给人的印象是Ruby程序员不一定要考虑扩展语言,这就是您可以使用Ruby进行编程。但这非常酷。)

  7. 在Python中,您实际上可以反汇编由编译器生成的字节码,甚至可以从头开始编写自己的字节码,然后让解释器执行该字节码(我自己完成了此操作,这很费力,但很有趣)。

  8. Python有很好的解析库。您可以将Python代码解析为抽象语法树,然后使用AST模块对其进行操作。PyParsing模块可用于解析任意语言,例如您设计的语言。从理论上讲,您可以根据需要使用Python编写第一个语言编译器(它可以生成C,汇编语言甚至Python输出)。

这种调查方法可以与更正式的方法配合使用,因为您将开始认识到您使用的语言学习过的概念,反之亦然。

玩得开心!


不是要深入研究python,而是要害。这个孩子已经有N种语言,可以表示N种语言;N的增加不会有太大的区别。以C为例。这是标准的。它有很多图书馆。它是跨平台的(当您遵循标准时)。您可以反汇编输出。您可以编写CFront。等等。
伊恩

1

好吧,我认为您的问题可以改写为“计算机科学学位的核心实践概念是什么”,并且总的答案当然是获得您自己的计算机科学学士学位。

从根本上讲,您可以通过以下操作来创建自己的编程语言编译器:读取文本文件,从文本文件中提取信息,然后根据从文本文件中读取的信息对文本进行转换,直到将其转换为可以被读取的字节为止。加载程序(请参阅Levine的链接程序和加载程序)。初次编译时,琐碎的编译器是一个相当严格的项目。

操作系统的心脏是内核,它管理资源(例如,内存分配/重新分配),并在任务/​​进程/程序之间切换。

汇编程序是text-> byte转换。

如果您对此东西感兴趣,我建议您在Linux中编写一个X86汇编器,该汇编器支持标准X86汇编的某些子集。这将是一个相当简单的切入点,并向您介绍这些问题。这不是一个小项目,并且会教给您很多东西。

我建议用C编写它;C是该级别工作的通用语言。


1
另一方面,这是非常高级的语言的好地方。只要可以指定文件中的各个字节,就可以使用任何语言制作编译器/汇编器(这更容易)。说,perl。或VBA。天堂,可能性!
伊恩


1

我很荣幸能以PDP-8作为我的第一门汇编语言。PDP-8只有六个指令,这些指令非常简单,很难想象它们是由几个谨慎的组件实现的,实际上它们是。它确实消除了计算机的“魔力”。

相同启示的另一个途径是Knuth在他的示例中使用的“混合”汇编语言。如今,“ Mix”似乎已过时,但仍然具有DE神秘化的效果。


0

编译器和编程语言(以及所有内容,包括构建一个系统的语言,例如定义有限的语法和转换为汇编程序)是一项非常复杂的任务,需要对整个系统有大量的了解。这种类型的课程通常在大学的3/4年级Comp Sci课程中提供。

我强烈建议您首先对操作系统有一个更好的了解,以及如何编译/执行现有的语言(即,本机(C / C ++),在VM(Java)中或由解释器(Python / Javascript))。

我相信我们在第二年的操作系统课程中使用了亚伯拉罕·西尔伯斯查茨(Abraham Silberschatz),彼得·B·加尔文(Peter B.Galvin)和格雷格·加涅(Greg Gagne)所著的《操作系统概念》一书。这是一本非常出色的书,对操作系统的每个组件进行了全面的演练-有点昂贵,但非常值得,而且旧的/使用过的副本应该随处可见。


操作系统概念?构建编译器只需要很少的钱。需要的是对软件体系结构的理解:寻址空间,堆栈,线程(如果他想学习编译器,他会更好地了解并行性及其未来)。
伊拉·巴克斯特

在说他想学习语言设计和编译器后,他立即说他想学习OS。
David Thornley

@伊拉克-同意。我从来没有说过要理解OS是构建编译器/语言所必需的,只是简单地解释说这可能是一个更简单的起点。每个人都在关注问题的“编译器”方面,但他也提到他希望更好地了解OS和库。对于仍在学习架构的15岁儿童而言,了解内存管理,线程,锁定,I / O等将比学习如何使用yacc(IMHO)定义语法要有用得多

抱歉...错过了要学习(构建?)操作系统的要点。我的观点是:他不需要很多OS知识的编译器。实际上,这几乎是一个完全不同的主题,除了在其中编译器和OS交互以实现某种共同目的的地方。(例如,Multics要求其PL / 1编译器以某些方式构建函数调用以启用全局VM。)
伊拉·巴克斯特

0

这是一个很大的话题,但是与其花大笔的“读书,孩子”来代替你,不如说我会很高兴地为您提供指示,以帮助您将头缠在脑子里。

大多数编译器和/或解释器的工作方式如下:

令牌化:扫描代码文本,并将其打入标记列表。

这一步可能很棘手,因为您不能只将字符串分割成空格,而是必须识别出这if (bar) foo += "a string";是8个标记的列表:WORD,OPEN_PAREN,WORD,CLOSE_PAREN,WORD,ASIGNMENT_ADD,STRING_LITERAL,TERMINATOR。如您所见,简单地将源代码分割成空格是行不通的,您必须按顺序读取每个字符,因此,如果遇到字母数字字符,您将继续读取字符,直到遇到非字母数字字符,并且该字符串刚刚读到的是一个WORD,稍后再进行分类。您可以自己决定令牌生成器的粒度:它是"a string"作为一个称为STRING_LITERAL的令牌吞入以待稍后解析,还是会看到"a string" 像OPEN_QUOTE,UNPARSED_TEXT,CLOSE_QUOTE或其他内容,这只是编码时必须自行决定的众多选择之一。

Lex:所以现在您有了令牌列表。您可能用诸如WORD之类的歧义标记了一些标记,因为在第一遍过程中,您无需花费太多精力来尝试找出每个字符串的上下文。因此,现在再次阅读源标记列表,并根据您的语言中的关键字,使用更具体的标记类型对每个歧义标记进行重新分类。因此,您有一个诸如“ if”之类的单词,并且“ if”在特殊符号列表中称为“符号IF”,因此您将该令牌的符号类型从WORD更改为IF,而不在特殊关键字列表中的任何WORD (例如WORD foo)是IDENTIFIER。

解析:因此,现在您打开if (bar) foo += "a string";了一个清单如下的词汇标记:IF OPEN_PAREN IDENTIFER CLOSE_PAREN IDENTIFIER ASIGN_ADD STRING_LITERAL TERMINATOR。该步骤是将标记序列识别为语句。这是解析。您可以使用如下语法:

STATEMENT:= ASIGN_EXPRESSION | IF_STATEMENT

IF_STATEMENT:= IF,PAREN_EXPRESSION,STATEMENT

ASIGN_EXPRESSION:= IDENTIFIER,ASIGN_OP,VALUE

PAREN_EXPRESSSION:= OPEN_PAREN,VALUE,CLOSE_PAREN

值:=标识符| STRING_LITERAL | PAREN_EXPRESSION

ASIGN_OP:=等于| ASIGN_ADD | ASIGN_SUBTRACT | ASIGN_MULT

使用“ |”的作品 术语之间的意思是“匹配任何这些术语”,如果术语之间有逗号,则表示“匹配此术语序列”

你怎么使用这个?从第一个令牌开始,尝试将令牌序列与这些产品匹配。因此,首先您尝试将令牌列表与STATEMENT匹配,因此您阅读了STATEMENT的规则,并说“ STATEMENT是ASIGN_EXPRESSION或IF_STATEMENT”,因此您首先尝试匹配ASIGN_EXPRESSION,因此您查找了ASIGN_EXPRESSION的语法规则并显示“ ASIGN_EXPRESSION是IDENTIFIER,后跟一个ASIGN_OP,后跟一个VALUE,因此您查找IDENTIFIER的语法规则,就会发现IDENTIFIER没有语法ruke,这意味着IDENTIFIER是一个“终端”,这意味着不需要进行解析以匹配它,因此您可以尝试直接将其与令牌匹配。但是您的第一个源令牌是IF,并且IF与IDENTIFIER不同,因此匹配失败。现在怎么办?您回到STATEMENT规则并尝试匹配下一项:IF_STATEMENT。您查找IF_STATEMENT,以IF开头,查找IF,如果IF是一个终端,将终端与您的第一个令牌进行比较,如果令牌匹配,真棒,继续下去,下一个是PAREN_EXPRESSION,查找PAREN_EXPRESSION,它不是终端,它是第一个术语, PAREN_EXPRESSION以OPEN_PAREN开始,查找OPEN_PAREN,这是一个终端,将OPEN_PAREN与您的下一个令牌匹配,它与....依此类推。

实现此步骤的最简单方法是,您拥有一个名为parse()的函数,该函数将要与之匹配的源代码令牌以及与之匹配的语法术语传递给该函数。如果语法术语不是终结符,则递归:再次调用parse(),将相同的源令牌和该语法规则的第一项传递给parse()。这就是为什么它被称为“递归下降解析器”的原因parse()函数返回(或修改)您在读取源令牌中的当前位置,它实际上将返回匹配序列中的最后一个令牌,然后您继续对从那里解析。

每次parse()与ASIGN_EXPRESSION之类的生产匹配时,您都会创建一个代表该代码段的结构。此结构包含对原始源令牌的引用。您开始构建这些结构的列表。我们将整个结构称为抽象语法树(AST)

编译和/或执行:对于语法中的某些生成,您已经创建了处理函数,如果给定AST结构,该处理函数将编译或执行该AST块。

因此,让我们看看您的AST的类型为ASIGN_ADD。因此,作为解释器,您具有ASIGN_ADD_execute()函数。该函数作为与的解析树相对应的AST的一部分传递foo += "a string",因此此函数查看该结构,并且知道该结构中的第一项必须是IDENTIFIER,第二项是VALUE,因此ASIGN_ADD_execute()将VALUE术语传递给VALUE_eval()函数,该函数返回表示内存中已评估值的对象,然后ASIGN_ADD_execute()在变量表中查找“ foo”,并存储对eval_value()返回值的引用功能。

那是翻译。相反,编译器将具有处理函数,而不是将AST转换为字节代码或机器代码。

使用Flex和Bison之类的工具可以使第1步到第3步,甚至第4步更容易。(又名Lex和Yacc),但从头开始编写解释器可能是任何程序员都可以完成的最有力量的工作。在提出这一挑战之后,所有其他编程挑战似乎都是微不足道的。

我的建议是从小开始:一门小语言,一门小语法,然后尝试解析并执行一些简单的语句,然后从那里开始发展。

阅读这些,祝您好运!

http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c

http://en.wikipedia.org/wiki/Recursive_descent_parser


2
当人们考虑进行编译时,您犯了一个我认为是经典的错误:即认为问题在于解析。从技术上讲,解析很容易;有很多很棒的技术可以做到这一点。编译的难点是语义分析,在程序表示的高低层次上进行优化以及代码的生成,这些天越来越重视PARALLEL代码。您可以在回答中完全忽略这一点:“编译器具有处理程序函数,可以将AST转换为字节码”。隐藏了50多年的编译器理论和工程学。
艾拉·巴克斯特

0

计算机领域之所以很复杂,是因为它有时间朝着许多方向发展。从本质上讲,它只涉及计算机。

我最喜欢的非常基本的计算机是哈里·波特的中继计算机。它提供了计算机在基本级别上的工作方式的味道。然后,您可以开始理解为什么需要诸如语言和操作系统之类的东西。

问题是,不了解需要什么就很难理解任何东西。祝你好运,不要只读东西。 东西。



-1

另一本很好的入门书是N. Wirth于1986年创作的“ Compilerbau”(编译器结构),该书长约100页,并解释了玩具语言PL / 0的简洁,精心设计的代码,包括解析器,代码生成器和虚拟机。它还显示了如何编写一个语法分析器,以EBNF表示法进行语法分析。这本书是德文版,但我写了一个摘要,并作为练习将代码翻译成Python,请参见http://www.d12k.org/cmplr/w86/intro.html


-1

如果您有兴趣了解编程语言的本质,建议您阅读PLAI(http://www.cs.brown.edu/~sk/Publications/Books/ProgLangs/)一书,以了解概念和他们的执行。它还将帮助您设计自己的语言。


-1

如果您真的对编译器感兴趣,并且以前从未有过兴趣,则可以从设计用于计算算术公式(如Eric提到的DSL的一种)的计算器开始。对于这种编译器,您需要考虑许多方面:

  • 允许的数字
  • 允许的运营商
  • 运营商优先事项
  • 语法验证
  • 可变查找机制
  • 循环检测
  • 优化

例如,您具有以下公式,您的计算器应该能够计算x的值:

a = 1
b = 2
c = a + b
d = (3 + b) * c
x = a - d / b

从一开始,这并不是一个非常困难的编译器,但是可以使您更多地了解编译器的基本概念,并且还可以帮助您提高编程技能和控制代码质量(这实际上是一个完美的问题,测试驱动开发TDD可能适用于提高软件质量。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.