解释器和逆波兰符号

The Interpreter and Reverse Polish Notation

使用高级语言,interpreter/compiler 的工作是在执行前将数学表达式从中缀表示法转换为后缀或前缀。事实上,是否需要将中缀表达式转换为 "processed"?我想稍微了解一下发生了什么事 "behind the scences"。不过,我对堆栈的概念及其在 RPN 中的使用很满意。

这完全取决于特定语言的设计——只要保证计算的正确性,无论它们是以中缀、后缀还是任何其他方式完成的。

(是否反转)计算机内部不使用波兰表示法:它完全供人类使用。它之所以流行,是因为它是一种为电子计算器表示相当复杂的算术表达式的方法,当时电子计算器还不够复杂,无法理解运算符的优先级和括号,就像今天的计算器一样。 "grew up" 使用这些计算器的工程师习惯于以这种方式思考,因此自然而然地,通用计算机上的第一个计算器程序以相同的方式工作。

当您用编程语言编写表达式时,解析器(解释器或编译器中理解该语言的部分)将该表达式转换为一棵树,其中树的每个节点都是一个操作,每个子节点该节点的是表达式的操作数。例如,

f(3 + 4)

可能是一个 "function call" 节点,其第一个子节点是名称 f,其第二个子节点是 "add" 节点。 "add" 节点的子节点是 "literal" 个节点(即值),其值为 3 和 4。

这棵树被称为抽象语法树 (AST),因为它是一棵结构与语言语法分离的树(即语法被抽象掉)。在像 gcc 这样理解多种不同语言的编译器中,每种语言的解析器都会生成相同类型的 AST,并且原始语言是使用 RPN 还是数学风格的中缀表示法,或者仅使用函数调用都无关紧要。

然后解析器将其提供给后端。在解释器中,后端只会一次评估每个节点,很可能从底部开始。也就是说,它首先用值 7 替换 "add" 节点及其子节点,然后查找名为 f 的函数,然后用以下结果替换 "function call node" 及其子节点函数调用。

编译器将树翻译成指令序列。在此示例中,它们可能类似于:

load 3 into register 0
load 4 into register 1
add registers 0 and 1, and put the answer in register 0
jump to the code for f, which expects its argument in register 0
use the result in register 0

显然,这些说明并没有那么冗长:它们是用 汇编语言 编写的,通常特定于目标体系结构(您的计算机类型重新编译)。我不会展示整个汇编程序,但添加指令可能如下所示:

add r0, r0, r1

编译器的最后一步是汇编器,然后将汇编程序的每条指令翻译成一个数字,实际CPU可以理解。

其中没有任何 RPN 或操作数堆栈。 "stack" 你听到的程序使用(例如本网站名称中的那个)是一个(某种)自动增长的内存区域。该程序可以使用它来存储太大而无法放入寄存器的东西,或者会被函数调用清除的东西(因为被调用的函数想要使用您正在使用的寄存器)。

已经有CPU architectures that actually did use a stack, and the assembly language for the CPU looked a bit like RPN. The "virtual machine" used by PostScript is an example, and Lisp Machinesare/were一个真正的硬件例子。不过,当今流行的 CPU 架构没有以这种方式工作。