
コンピューティングにおいて、インタプリタとは、ソースコードを機械語にコンパイルせずに実行するソフトウェアのことです。インタプリタ型ランタイム環境は、CPUネイティブの実行可能コードを処理する環境とは異なります。CPUネイティブの実行可能コードは、実行前にソースコードを変換する必要があります。インタプリタは、ソースコードをバイトコードなどの中間形式に変換する場合があります。ハイブリッド環境では、バイトコードを直接解釈するのではなく、 .NETやJavaのように、ジャストインタイムコンパイルによってバイトコードを機械語に変換する場合があります。
インタプリタが広く普及する以前は、コンピュータプログラムの実行は、ソースコードを機械語に翻訳・コンパイルするコンパイラに依存することが多かった。LispやBASICの初期の実行環境は、ソースコードを直接解析することができた。その後、 Perl、Raku、Python、MATLAB、Rubyなどの言語向けに、実行前にソースコードを中間形式に変換して実行パフォーマンスを向上させる実行環境が開発された。
インタプリタで実行されるコードは、互換性のあるインタプリタを備えたあらゆるプラットフォームで実行できます。各プラットフォーム用に実行可能ファイルを作成する必要がなく、同じコードをあらゆるプラットフォームに配布できます。通常、各プログラミング言語は特定の実行環境に関連付けられていますが、言語は異なる環境でも使用できます。ALGOL 、Fortran、COBOL、C、C++など、従来コンパイルと関連付けられてきた言語向けにインタプリタが構築されています。
コンピュータ黎明期には、当時のハードウェアではインタプリタとインタプリタコードの両方をサポートできなかったこと、また当時の典型的なバッチ環境ではインタプリタの利点が制限されていたことから、インタプリタよりもコンパイラの方が一般的でよく使われていた。[ 1 ]
インタプリタは、当時のコンピュータの制約 (例えば、プログラムの記憶領域の不足や、浮動小数点数のネイティブサポートの欠如) の中でプログラミングを容易にするために、1952 年という早い時期から使用されていました。インタプリタはまた、低レベルの機械語間の翻訳にも使用され、まだ開発中のマシン向けにコードを記述し、既に存在するコンピュータでテストすることを可能にしました。[ 2 ]最初のインタプリタ型高レベル言語はLispでした。Lisp は、 Steve RussellによってIBM 704コンピュータに初めて実装されました。Russell は、 John McCarthyの論文「Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I」を読み、(McCarthy を驚かせたことに) Lisp のeval関数が機械語で実装できることに気づきました。[ 3 ]その結果、Lisp プログラムを実行したり、より正確には「Lisp 式を評価したり」できる、動作する Lisp インタプリタができました。
編集インタプリタの開発は、対話型コンピューティングの必要性によって促進されました。1960年代には、タイムシェアリングシステムの導入により、複数のユーザーが同時にコンピュータにアクセスできるようになり、リアルタイムでコードを管理・変更するために編集インタプリタが不可欠となりました。最初の編集インタプリタは、おそらくメインフレームコンピュータ向けに開発されたもので、プログラムをその場で作成・変更するために使用されました。編集インタプリタの初期の例としては、1960年代後半にPDP-1コンピュータ向けに開発されたEDT(Editor and Debugger for the TECO)システムが挙げられます。EDTは、コマンドとマクロを組み合わせてプログラムの編集とデバッグを可能にし、現代のテキストエディタや対話型開発環境への道を開きました。
通訳者の代表的な用途としては、以下のようなものがある。
解釈オーバーヘッドとは、ネイティブ(コンパイル済み)コードではなく、インタプリタを介してコードを実行する際の実行時コストのことです。インタプリタはネイティブコードで同等の機能を実現するために複数のマシンコード命令を実行するため、解釈は遅くなります。特に、変数へのアクセスは、コンパイル時ではなく実行時に識別子を記憶場所にマッピングする必要があるため、インタプリタでは遅くなります。[ 4 ]しかし、開発の高速化(編集・ビルド・実行サイクルの短縮などの要因による)は、特にプロトタイピングやテストのように編集・ビルド・実行サイクルが頻繁に行われる場合、実行速度の高速化よりも価値が高くなります。[ 4 ] [ 5 ]
インタプリタは、高速な実行時パフォーマンスなどの目的を達成するために、ソース コードからプログラムの中間表現(IR) を生成することがあります。コンパイラも IR を生成することがありますが、コンパイラは後で実行するためのマシン コードを生成するのに対し、インタプリタはプログラムを実行する準備をします。これらの目的の違いにより、IR の設計も異なります。多くのBASICインタプリタは、キーワードをジャンプ テーブルで命令を見つけるために使用できる1バイトのトークンに置き換えます。[ 4 ] PBASICインタプリタなどの一部のインタプリタは、バイト指向ではなくビット指向のプログラム メモリ構造を使用することで、さらに高いレベルのプログラム圧縮を実現しています。この構造では、コマンド トークンはおそらく 5 ビットを占め、名目上「16 ビット」の定数は3、6、10、または 18 ビットを必要とする可変長コードに格納され、アドレス オペランドには「ビット オフセット」が含まれます。多くの BASIC インタプリタは、トークン化された内部表現を保存して読み戻すことができます。
インタプリタを使用した開発速度とコンパイラを使用した実行速度の間には、さまざまなトレードオフが存在します。一部のシステム(Lispなど)では、インタプリタで生成されたコードとコンパイルされたコードが相互に呼び出し、変数を共有できます。これは、ルーチンがインタプリタでテストおよびデバッグされた後、コンパイルして、他のルーチンを開発している間に高速な実行の恩恵を受けることができることを意味します。[ 6 ]
解釈とコンパイルの初期段階は似ているため、インタプリタはコンパイラと同じ字句解析器と構文解析器を使用し、結果として得られる抽象構文木を解釈する可能性がある。
C++で書かれた式インタープリタ。
import std ;using std :: runtime_error ; using std :: unique_ptr ; using std :: variant ;// 抽象構文木用のデータ型enum class Kind : char { VAR , CONST , SUM , DIFF , MULT , DIV , PLUS , MINUS , NOT };// 前方宣言クラスNode ;class Variable { public : int * memory ; };class Constant { public : int value ; };class UnaryOperation { public : unique_ptr <Node> right ; } ;class BinaryOperation { public : unique_ptr <Node> left ; unique_ptr <Node> right ; } ;式= variant < Variable , Constant , BinaryOperation , UnaryOperation >を使用する;class Node { public : Kind kind ; Expression e ; };// インタープリタプロシージャ[[ nodiscard ]] int executeIntExpression ( const Node & n ) { int leftValue ; int rightValue ; switch ( n -> kind ) { case Kind :: VAR : return std :: get < Variable > ( n . e ). memory ; case Kind :: CONST : return std :: get < Constant > ( n . e ). value ; case Kind :: SUM : case Kind :: DIFF : case Kind :: MULT : case Kind :: DIV : const BinaryOperation & bin = std :: get < BinaryOperation > ( n . e ); leftValue = executeIntExpression ( bin . left . get ()); rightValue = executeIntExpression ( bin . right . get ()); switch ( n . kind ) { case Kind :: SUM : return leftValue + rightValue ; case Kind :: DIFF : return leftValue - rightValue ; case Kind :: MULT : return leftValue * rightValue ; case Kind :: DIV : if ( rightValue == 0 ) { throw runtime_error ( "Division by zero" ); } return leftValue / rightValue; } case Kind :: PLUS : case Kind :: MINUS : case Kind :: NOT : const UnaryOperation & un = std :: get < UnaryOperation > ( n . e ); rightValue = executeIntExpression ( un . right . get ()); switch ( n . kind ) { case Kind :: PLUS : return + rightValue ; case Kind :: MINUS : return - rightValue ; case Kind :: NOT : return ! rightValue ; } default : std :: unreachable (); } }ジャストインタイム(JIT)コンパイルとは、実行時に中間フォーマット(バイトコードなど)をネイティブコードに変換するプロセスです。これによりネイティブコードが実行されるため、インタープリタを使用する際の実行時コストを回避しつつ、インタープリタ開発の要因となった利点の一部を維持することができます。