解释器(
英语:Interpreter),又译为直译器,是一种电脑程序,能够把高级
编程语言一行一行直接转译运行。解释器不会一次把整个程序转译出来,只像一位“中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它每转译一行程序叙述就立刻运行,然后再转译下一行,再运行,如此不停地进行下去。
在开始之前有必要再次强调:下面介绍的解释器是一个
源代码解释器。也就是说,解释器在执行时,每次读入一条语句,并且根据这条语句执行特定的操作;然后再读入下一条语句,依次类推。这与
伪代码解释器是有所区别的,例如早期的Java运行时系统。两者的区别在于:
源代码解释器直接对程序的源代码
解释执行;而
伪代码解释器先将程序的源代码转化为某种与机器无关的
中间代码,然后再执行中间代码。相比之下,
源代码解释器更易于创建,并且不需要一个独立的编译过程。
Small BASIC解释器包括两个主要的子系统:一个是表达式解析器,负责处理数字表达式;另一个是解释器,负责程序的实际执行。对于前者,可采用本书第二章所介绍的表达式解析器。但是在这里做了某些改进,使得解析器能够解析包含在程序语句中的数字表达式,而不是只能解析孤立的表达式。
解释器子系统和解析器子系统包含在同一个解释器类中,该类名为SBasic。尽管从理论上讲可以使用两个独立的类:一个包含解释器,另一个包含
表达式解析器;但是将两者用同一个类来实现的代效率会更高,因为表达式解析器和解释器的代码是密不可分的。例如,两个子系统都操作保存着程序代码的同一个字符
数组。如果将它们分别安排在两个类中,将会增加可观的额外开销,并导致性能上的损失和功能上的重复。此外,由于程序解释的任务繁重,而解析
表达式只是其中的一部分,因此将整个解释机制包含在单个类中是很有意义的。
解释器执行时,每次从程序的
源代码中读入一个
标识符。如果读入的是关键字,解释器就按照该关键字的要求执行规定的操作。举例来说,当解释器读入一个PRINT后,它将打印PRINT之后的字符;当读入一个GOSUB时,它就执行指定的
子程序。在到达程序的结尾之前,这个过程将反复进行。可以看到,解释器只是简单地执行程序指定的动作。
Perl,
Python,
MATLAB,与
Ruby是属于第二种方法,而
UCSD Pascal则是属于第三种方式。在转译的过程中,这组高级语言所写成的程序仍然维持在源代码的格式(或某种中继语言的格式),而程序本身所指涉的动作或行为则由解释器来表现。
使用解释器来运行程序会比直接运行编译过的机器码来得慢,但是相对的这个直译的行为会比编译再运行来得快。这在程序开发的雏型化阶段和只是撰写试验性的代码时尤其来得重要,因为这个“编辑-直译-除错”的循环通常比“编辑-编译-运行-除错”的循环来得省时许多。
在使用解释器来达到较快的开发速度和使用编译器来达到较快的运行进度之间是有许多妥协的。有些系统(例如有一些
LISP)允许直译和编译的代码互相调用并共享变量。这意味着一旦一个子程序在解释器中被测试并除错过之后,它就可以被编译以获得较快的运行进度。许多解释器并不像其名称所说的那样运行原始代码,反而是把原始代码转换成更压缩的内部格式。举例来说,有些
BASIC的解释器会把
keywords取代成可以用来在jump table中找出相对应指令的单一
byte符号。解释器也可以使用如同编译器一般的文字分析器(lexical analyzer)和
语法分析器(parser)然后再转译产生出来的
抽象语法树(abstract syntax tree)。
考量程序运行之前所需要分析的时间,存在了一个介于直译与编译之间的可能性。例如,用Emacs Lisp所撰写的源代码会被编译成一种高度压缩且优化的另一种
Lisp源代码格式,这就是一种
字节码C虚拟机(不是真的硬件,而是一种字节码解释器)的机器码。这个方式被用在Open Firmware系统所使用的
Forth