我最近阅读了整本龙书(只是为了好玩,我并没有真正计划实现一个真正的编译器),而且我的脑子里还悬着这个大问题。
实现编译器和解释器之间有什么不同?
编译器由我编写:
现在,很明显,解释器也有与编译器相同的词法分析器和解析器 但那之后呢?
它是否“读取”语法树并直接执行它? (有点像指针指向树中的当前节点,执行是一个大树遍历加上调用堆栈的内存管理)(如果是这样,它是如何做到的?我希望执行比检查它是什么类型的节点的巨大的switch语句更好
它会生成3个地址代码并解释吗? (如果是这样,它是如何做到的?再次,我正在寻找比一英里长的开关声明更优雅的东西)
此外,“虚拟机”的概念在哪些方面切入?你在一种语言中使用虚拟机是什么? (要清楚我的无知程度,对我来说虚拟机是VMWare,我不知道VM的概念如何应用于编程语言/执行程序)。
如您所见,我的问题非常广泛。我主要不仅要寻找使用哪种方法,而且主要是先了解大概念,然后详细了解它的工作原理。我想要丑陋的原始细节。显然,这更多是为了阅读对事物的参考,而不是期望你在这里回答所有这些细节。
谢谢!
丹尼尔
例如:VB6显然既是编译器又是解释器。我现在了解编译器部分。但是,我无法理解,当在IDE内部运行时,它可以让我在任意点停止程序,更改代码,并使用新代码继续执行。 这只是一个很小的例子,它不是我正在寻找的答案。正如我在下面解释的那样,我想要理解的是在我有一个解析树之后会发生什么。编译器将以“目标”语言从中生成新代码。口译员做什么?
感谢您的帮助!
答案 0 :(得分:8)
编译器是一种程序,它将一种编程语言中的程序转换为另一种编程语言中的程序。就是这样 - 简单明了。
解释器将编程语言翻译成其语义。
x86芯片是x86机器语言的解释器。
Javac是java到java虚拟机的编译器。 java,可执行应用程序,是jvm的解释器。
有些口译员分享一些编译元素,因为他们可能会将一种语言翻译成另一种更容易理解的内部语言。
解释器通常(但并非总是)具有读取 - 评估 - 打印循环。
答案 1 :(得分:7)
简短回答:
如何实施这些方法还有很大的余地。解释器可以生成本机机器代码然后执行该代码,而虚拟机的编译器可以生成p代码而不是机器代码。像Forth这样的线程解释语言在字典中查找关键字并立即执行它们相关的本机代码函数。
编译器通常可以更好地进行优化,因为他们有更多的时间来研究代码并生成一个文件供以后执行;口译员有更少的时间进行优化,因为他们倾向于“一见钟情”地执行代码
在后台优化的解释器,也可以学习更好的方法来执行代码
摘要:差异实际上归结为“为以后执行准备代码”或“立即执行代码”
答案 2 :(得分:6)
程序是您要完成的工作的描述。
编译器将高级描述转换为更简单的描述。
解释程序会读取操作说明以及完成工作。
编译器从不完成工作。口译员始终完成工作。
答案 3 :(得分:4)
两者都有很多共同点(例如词法解析器),并且在差异上存在分歧。我这样看:
经典定义将是编译器将符号流解析并转换为可由CPU运行的字节流,而解释器执行相同的操作但将其转换为必须在一个软件上执行(例如JVM,CLR)。
然而人们称'javac'为编译器,因此编译器的非正式定义必须完成源代码作为单独的步骤,而解释器没有“构建”步骤(例如PHP, Perl的)。
答案 4 :(得分:3)
它不像过去那样清晰。它曾经是构建一个解析树,绑定它并执行它(通常在最后一秒绑定)。
BASIC主要以这种方式完成。
你可以声称在没有JIT的情况下运行字节码(java / .net)的东西都是interpriters - 但不是传统意义上的,因为你仍然需要'编译'到字节码。
旧学校的区别在于:如果它生成CPU代码,那么它就是编译器。如果您直接在编辑环境中运行它并且可以在编辑时与它进行交互,那么它就是一个interpriter。
这远不如实际的龙书那么正式 - 但我希望它能提供丰富的信息。
答案 5 :(得分:2)
如果我的经验表明什么;
在镜头中,差异可能就像
一样简单case '+':
symtbl[var3] = symtbl[var1] + symtbl[var2];
break;
之间,
case '+':
printf("%s = %s + %s;",symtbl[var3],symtbl[var1],symtbl[var2]);
break;
(如果您使用其他语言或(虚拟)机器说明,则无关紧要。)
答案 6 :(得分:2)
关于你的问题的这一部分,其他答案尚未真正解决:
此外,这个概念在哪个方面 “虚拟机”切入?做什么 你在一个虚拟机中使用虚拟机 语言
JVM或CLR等虚拟机是一个抽象层,允许您为编译为在VM上运行的完全不同的语言重用JIT编译器优化,垃圾收集和其他实现细节。
它们还可以帮助您使语言规范更加独立于实际硬件。例如,虽然C代码在理论上是可移植的,但如果您真的想要生成可移植代码,则必须经常担心字节顺序,类型大小和变量对齐等问题。对于Java,JVM在这些方面非常明确,因此语言设计者及其用户不必担心它们; JVM实现者的工作是在实际硬件上实现指定的行为。
答案 7 :(得分:1)
一旦解析树可用,就有几种策略:
1)直接解释AST(Ruby,WebKit的原始解释器) 2)代码转换 - >字节码或机器码
要实现编辑并继续,必须重新计算和移动程序计数器或指令指针。这需要IDE的合作,因为代码可能是在黄色小箭头之前或之后插入的。
可以这样做的一种方法是将程序计数器的位置嵌入到解析树中。例如,可能会有一个称为“break”的特殊语句。程序计数器只需在“break”指令后定位即可继续运行。
此外,您必须决定要对当前堆栈帧(以及堆栈上的变量)执行的操作。也许弹出当前堆栈,复制变量或保持堆栈,但在GOTO中修补并返回到当前代码。
答案 8 :(得分:0)
根据您的步骤列表:
一个非常简单的解释器(如早期的BASIC或TCL)一次只执行第一步和第二步。然后在继续执行下一行的同时丢弃大部分结果。其他3个步骤根本不会执行。
答案 9 :(得分:0)
如果您正在寻找一本书,Structure and Interpretation of Computer Programs("向导书")是一个从翻译概念入手的好地方。您只能处理Scheme代码,它可以被遍历,评估和传递,就像它是AST一样。
另外,Peter Norvig有一个简短的例子,解释了使用Python的主要思想(在评论中有更多的例子),这是维基百科上的另一个small example。
就像你说的那样,它是一个树遍历,至少对于按值调用它是一个简单的:每当你看到一个运算符时,先评估操作数,然后应用运算符。返回的最终值是程序(或给REPL的语句)的结果。
请注意,您并不总是必须明确地进行树遍历:您可以以接受访问者的方式生成AST(我认为SableCC会这样做),或者对于非常小的语言,例如小型语言用于演示解析器生成器的算术语法,您只需在解析过程中评估结果。
为了支持声明和作业,您需要保持环境。就像你评估"加"通过添加操作数,您可以通过在环境中查找来评估函数,变量等的名称。支持范围意味着将环境视为堆栈,并在适当的时间推送和弹出。通常,解释器的复杂程度取决于您支持的语言功能。例如,口译员可以进行垃圾收集和内省。
对于VM:plinth和j_random_hacker将计算机硬件描述为一种解释器。反之亦然 - 口译员是机器;他们的指令恰好比真正的ISA更高级。对于VM风格的解释器,程序实际上类似于机器代码,albiet用于非常简单的机器。 Java bytecode只使用了几个"寄存器,"其中一个持有程序计数器。因此,VM解释器更像是硬件模拟器而不是上面链接的示例中的解释器。
但请注意,出于速度原因,默认的Oracle JVM的工作方式是将Java字节码指令的运行转换为x86指令("及时编译")。