インタプリタ

歴史

インタプリタという手法、すなわち、「そのハードウェアが直接解釈するのではないプログラム」を受け取り、「プログラムで実装された抽象的な、あるいは仮想上のコンピュータで解釈実行する」というプログラムの実行法は、コンピュータが登場した時から、ないしそれ以前からある。
万能チューリングマシンは、「どんなチューリングマシンについても、それを模擬できるチューリングマシン」というもので、ある種のエミュレータないしインタプリタであり、考察されたのは電子式のコンピュータの誕生する以前である。
EDSAC（実用的な機能を持ったプログラム内蔵方式の世界初の電子計算機とされている）において既に、ある種のインタプリタが実装されていたことが記録に残っている。同機におけるプログラミングの技法が書かれた The Preparation of Programs for an Electronic Digital Computer の chapter 2 の § 2-22 Interpretive subroutines で説明されているが、複素数演算などのサブルーチンを明示的にサブルーチンとして呼ぶのではなく、通常の加減算などと同様の形式のプログラムをインタプリタで解釈してそれらのサブルーチンを利用する、というものである。また日本においても、パンチカードを入力としてパッチパネルの配線によるプログラミングで処理するような機械で、配線によってある種のインタプリタのようなものを実装し、パンチカードの内容をデータとしてではなくプログラムのように扱う、というような例があると言われている。
最初の Lisp インタプリタはスティーブ・ラッセルが IBM 704 上に実装した。これにはエピソードがあり、ジョン・マッカーシーが「Lisp の論文」で「数学的」に示したものだったのであるが、マッカーシー自身は実装できるものだとは考えていなかった。それを、論文を読んだ、院生であったラッセルが、実装可能だと言って数学的な記述から変換して機械語で実装してみせたという。
1960年代には（現在のJavaなどと同様な）、プログラミング言語から中間表現にコンパイルし、それをインタプリタで実行する、というような手法も一般的になった（pコードマシンを参照）。

インタプリタの長所と短所

初期のインタプリタと、最近のインタプリタでは、長所や短所は異なる。短所をなくすべく（減らすべく）、しくみを変えてきた歴史があるためである。

開発時に修正作業が容易

プログラム開発中、プログラマは頻繁にソースコードに手を加える。コンパイラの場合、ソースコードを変更するたびにコンパイルし、リンクして実行ファイルを完成させないと、そのプログラムを実行できない。プログラムが大きくなると、ビルドの完了を待っている時間が長くなる。一方、インタプリタではソースコードをそのまま実行するか中間表現に変換するだけなので、ほとんど待つ必要がなく、修正がうまくいったかどうかのテストをより素早く確認できる。
この特性を利用したプログラムとしてREPLがある。入力を受け取る（Read）、入力の評価（Eval）、評価結果を提示する（Print）を繰り返す（Loop）、テスト環境の一つである。評価はインタプリタに限らずコンパイラで行なわれるかもしれないが、ユーザからの入力は常にソースコードの断片であり、それを逐次実行するという点で一種のインタプリタということもできる。実際、その挙動はソースコードを指定せずに起動したBASICなどのそれとよく似ている。しかし、テスト環境も兼ねているため、エラーが起きた場合のメッセージが詳細であることが多い。関数型言語や論理型言語では、通常は機械語にコンパイルする処理系であっても用意される。

配布する上でのメリットとデメリット

コンパイラは一般にソースコードを特定のプロセッサアーキテクチャの機械語命令列に変換するので、生成されるプログラムは特定のアーキテクチャのプロセッサでしか動かない。この変換は開発者の環境で一度だけ行われ、そのバイナリが配布され、ユーザー側では変換を行う必要がない。クロスコンパイラを使えば、他のプロセッサアーキテクチャ向けのバイナリを生成することができる。
インタプリタの場合、ソースコードを配布できる。その変換はユーザー側で行う必要があるが、特定のアーキテクチャに依存しないプログラムの配布が可能である。ただし、その場合ユーザーのマシン上に適当なインタプリタが実装されていなければならない。インタプリタとソースコードを同時に提供する必要があるなら、単に実行ファイルを配布した場合よりインストールが全体として複雑化することもある。
インタプリタ用コードは人間が容易に読むことができるため、著作権保護の観点から問題があるとする見方もある。しかし、そのための様々な暗号化や難読化のシステムも存在する。バイトコードのような中間コードを配布する場合、ある程度は難読化と同様の効果があるが、バイトコードを逆コンパイラあるいは逆アセンブラでデコードすることも可能である。抗逆コンパイル性のある難読化を考慮したバイトコードとする設計もありうる。

短所: 実行速度の遅さ

インタプリタ最大の短所は、コンパイラよりも実行時の性能が低いことである。この性能差は様々で、時には桁違いとなることもある。プログラムの実行時間はコンパイラよりもインタプリタの方が長いが、コンパイル時間と実行時間を合計すればインタプリタでの実行時間よりも長くなることがある。プロトタイピングとテストにおいては、この差が重要となる。
コンパイラではプログラム内の文の解析を実行前に1回だけ行うが、単純な実装のインタプリタではそれを文ごとに実行時に毎回行うため、実行性能が低くなる。単純な実装のインタプリタでは変数にアクセスする際も識別子とメモリ上の位置のマッピングを確認しなければならず、しかもそれを実行中に何度も行わなければならないので、遅くなるのである。
単純な実装のインタプリタ方式が速度が遅くなる最大の原因は、一命令ごとに switch 文を実行することにある。現代のCPUはパイプライン方式を採用しており、命令の先読みが可能でないと、実行速度が著しく低下する。switch 文の場所が命令の先読みが不可能であるため、単純な実装のインタプリタ方式は遅くなる。単純な実装のインタプリタ方式で実装された処理系を高速化（最適化）するための、最初にとられるステップが switch 文を廃止し、コンパイラ方式に切り替えることである。それゆえ、実装不可能というわけではないが、効果が薄いため、インタプリタ方式で実装する場合は、一般には、制御フロー解析や静的単一代入などを使った、複数の命令を超えての最適化が実装されないことが多い。
インタプリタによる開発の速さとコンパイラによる実行の速さの間で、様々な妥協案が考案されてきた。一部の LISP 処理系などでは、インタプリタのコードとコンパイルされたコードが相互に呼び出しあうことができ、変数も共有できる。そのため、あるルーチンをインタプリタで評価しデバッグした後、先行してコンパイルして実行性能を高めつつ、他のルーチンを開発し続けることができる。多くのインタプリタはソースコードをそのまま実行するわけではなく、よりコンパクトな内部形式に変換している。多くの BASIC インタプリタは予約語を1バイトのトークンに置換し、それをジャンプテーブルのインデックスとして使用する。PBASIC など一部のインタプリタでは、バイト単位ではなくビット単位でプログラムの短縮を行っており、例えばコマンドを5ビットで表し、一般に16ビットで表される定数をその数値の大きさに対応して可変長（3、6、10、18ビットなど）で表し、アドレスオペランドとして「ビットオフセット」を用意している。多くの BASIC インタプリタは独自にトークン化された内部表現を保存し、読み込むことができる。
インタプリタがコンパイラと同様の字句解析と構文解析を行い、その結果生成された抽象構文木を解釈することもある。

スレッデッドコード

それ自体はインタプリタの手法とも言えるしコンパイラの手法とも言える。そのどちらと言うよりも、中間表現の1種類というべきかもしれない。仮想関数テーブルやテーブルジャンプによるフロー制御に似ていなくもない。
スレッデッドコードは、呼び出されるべきサブルーチンのアドレスのみが順番に羅列されたものである。「直接スレッディング」の場合は、そのアドレスが指す先は機械語のサブルーチンである。他にもいくつかのバリエーションがある。「サブルーチン・スレッディング」は最も違うタイプのバリエーションで、アドレスのみではなく、機械語のCALL命令として羅列するので、ハードウェアのプロセッサで直接実行できる。これは実行のオーバーヘッドは極小だが、メモリ効率は悪い。サブルーチン・スレッディング以外のスレッデッドコードは、きわめて単純なインタプリタで実行できる。Forthでは「内部インタプリタ」と呼んでいる（これは、Forth言語自体を実装しているインタプリタである「外部インタプリタ」と対になっている）。

バイトコードインタプリタ

ソースコードを実行可能な形にするには、まず、ソースコードを構文木に変換する必要がある。構文木のまま、インタプリタ型の処理系で実行する処理系もあるが、構文木をさらに、中間コード（バイトコードなど）などの中間表現に変換してから実行する物もある。中間コードをバイトコードと呼んでいる処理系ではそのインタプリタをバイトコードインタプリタと呼ぶ。Java や .NET Framework のように、中間コードの仕様を公開しファイルに書き出すものもあるし、仕様は公開せず処理系内部だけで使用するものもある。動的コンパイルを使っているインタプリタは、内部で実機の機械語に変換し実行する。
インタプリタとコンパイラの間には様々な中間的実装が存在し、それぞれにプログラム実行前に行われる解析の度合いが異なる。例えば Emacs Lisp はバイトコードにコンパイルされ、LISP のソースを高度に圧縮し最適化した表現にしているが、それは機械語コードではない（したがって特定のプラットフォームに依存しない）。この「コンパイル」されたコードを解釈するのがバイトコードインタプリタである（それ自体は C で書かれている）。この場合のコンパイルされたコードは仮想機械の機械語コードであり、仮想機械はハードウェアで実装されておらず、バイトコードインタプリタとして実装されている。同様の手法は Open Firmware システムで使われている Forth コードでも使われている。ソース言語は「Fコード」（バイトコードの一種）にコンパイルされ、それを仮想機械が解釈実行する。他にPコードマシンなどがある。
コントロール・テーブルはコンパイラを通さなくとも生成でき、バイトコードインタプリタと同様の方法でカスタマイズされたインタプリタでの適切なアルゴリズム的制御構造を記述できる。

抽象構文木インタプリタ

インタプリタとコンパイラの中間的手法の1つとして、ソースコードを最適化された抽象構文木 (AST) に変換し、その木構造にしたがってプログラムを実行するか、実行時コンパイラでの機械語コード生成に使用する方法がある。この方式では各文を1回だけ構文解析する必要がある。バイトコードに比べると、ASTではプログラムの全体的構造や文と文の関係を保持でき（それらはバイトコードでは失われる）、圧縮するとさらにコンパクトな表現になる。そのため、実行時コンパイラにとってはバイトコードよりもASTの方が優れた中間表現だとして提案されてきた。また、実行時の解析もより優れたものにできる。
しかし、ASTはバイトコードよりも冗長であるため、インタプリタとしてはオーバーヘッドが大きくなるという問題がある。CRuby の場合は、1.8までは構文木インタプリタであったが、1.9では（開発中には YARV と呼ばれていた）バイトコードインタプリタに入れ替えられ、性能が向上した。

実行時コンパイル

インタプリタとコンパイラの境界をさらにぼやけさせる方式として、中間表現を実行時コンパイラ (JIT) でコンパイルし、実行時にネイティブの機械語にコンパイルする技法がある。これはネイティブなコードの実行効率を実現する代わり、ASTやバイトコードを最初にコンパイルする際に起動時間やメモリ使用量が増大するという欠点がある。これを補う技法として適応的最適化があり、インタプリタが実行中のプログラムを性能解析して最も頻繁に実行される部分をネイティブのコードにコンパイルする。これらの技法は1980年代の Smalltalk などの言語で使われ始めた。
実行時コンパイルは近年多くの言語処理系で採用されており、Java、.NET Framework、最近のJavaScript の実装でも JIT が採用されている。

トランスレータ方式

他のインタプリタ言語に変換して、ターゲット言語のインタプリタ上で実行する方式。例えば CoffeeScript は JavaScript に変換されて、JavaScript インタプリタ上で実行される。

デバッグ、教育用インタプリタ

通常C言語はコンパイラで処理されるが、デバッグ目的および教育目的のインタプリタ型のC言語の処理系もある。MS-DOS時代に、いくつかの製品が提供されていた。C-Terpなどがその様な製品の例である。C/C++のインタプリタはほかにCINTやChがある。

パンチカードのインタプリタ

パンチカードシステムにおいて、パンチカードを読み込んで、その内容を人間が読める形式（文字）でパンチカード上に印字する機械をインタプリタと呼ぶ。例えば、IBM 550 Numeric Interpreter (1930年) や IBM 557 Alphabetic Interpreter (1954年) がある。