プログラミング言語は通常、コンピュータプログラムの表現形式を設計し、開発した概念の実装(通常はインタプリタまたはコンパイラ)を作成することによって作成されます。 [ 1 ]インタプリタは、通常はテキスト形式の何らかのバリエーションでプログラムを読み込み、読み込んだ内容に基づいてアクションを実行するように設計されているのに対し、コンパイラはコードをオブジェクトコードなどのより低レベルの形式に変換します。[ 2 ]
プログラミング言語の設計においては、考慮すべき要素が多岐にわたります。要素によっては、互いに排他的な場合もあります(例えば、セキュリティと速度)。プログラミング言語がインタプリタ型かコンパイル型か、動的型付けか静的型付けか、継承の有無、そして言語の一般的な構文などを考慮する必要があるかもしれません。[ 3 ]言語の設計に関わる多くの要素は、その言語の目的によって決定されます。言語の対象ユーザー、独自の機能、そして目的を考慮することが重要です。[ 4 ]既存の言語に何が欠けているか、あるいは何が難しいかを検討し、言語が目的を果たすようにすることは良い習慣です。[ 4 ]
様々な専門家が、有用な設計原則を提案している。
多くのプログラミング言語には、コンパイラやインタプリタの少なくとも最初のバージョンを実装しやすくするための設計上の特徴が備わっています。例えば、Pascal、Forth、そして多くのアセンブリ言語は、ワンパスコンパイルをサポートするように特別に設計されています。
新しいプログラミング言語は、多くの場合、以前のプログラミング言語の(認識されている)問題を解決するために設計されます。通常、それらの言語で書かれたプログラムをよりシンプルにする機能を追加することで、この問題を解決します(ただし、これらの機能を追加すると、インタプリタやコンパイラはより複雑になる場合があります)。たとえば、組み込みの自動メモリ管理とガベージコレクションを備えた言語、組み込みの連想配列を備えた言語などです。
一方で、BCPL、Pascal、RPythonなどのように、コンパイルを困難にする機能を意図的に排除することで、自己ホスト型コンパイラを比較的簡単に作成できるように特別に設計されたプログラミング言語もいくつか存在する。
プログラミング言語の実装には、大きく分けて2つのアプローチがあります。[ 8 ]
これら二つの極端な例に加えて、多くの実装では、ジャストインタイムコンパイルやバイトコードインタープリタといったハイブリッドなアプローチが用いられている。
インタプリタは、JIT コンパイラや事前コンパイルに比べていくつかの利点があります。[ 10 ] 通常、インタプリタは読み込み-評価-出力のループをサポートしており、新しいプログラムの開発がはるかに速くなります。コンパイラは、開発者に編集-コンパイル-実行-デバッグのループを使用させるため、はるかに遅くなります。
一般的なプログラムは、事前コンパイルコンパイラでコンパイルすると、(コンパイル後)同じプログラムをJITコンパイラで処理して実行した場合よりも高速に実行されます。さらに、JITコンパイラで処理して実行した場合、同じプログラムをバイトコードなどのpコード中間言語に部分的にコンパイルしてアプリケーション仮想マシンで解釈した場合よりも高速に実行される可能性があり、アプリケーション仮想マシンで解釈した場合、純粋なインタプリタよりもはるかに高速に実行されます。[ 11 ]
理論的には、プログラミング言語はまず仕様が定められ、その後、その言語に対応するインタプリタやコンパイラが実装される(ウォーターフォールモデル)。しかし実際には、言語の実装過程で得られた知見が後のバージョンの言語仕様に影響を与えることが多く、結果としてプログラミング言語の設計と実装が一体化されることになる。
インタプリタとコンパイラはどちらも通常、何らかのシンボルテーブルを実装している。
インタプリタは、 Pythonなどの言語に見られるように、通常はテキストとして別のプログラムを読み込むプログラムです。[ 4 ]インタプリタはコードを読み込み、結果を直接生成します。[ 12 ]インタプリタは通常、コードを1行ずつ読み込み、それを解析して、コードを操作やアクションに変換して実行します。[ 13 ]
インタプリタは、パーサーと評価器の2つの部分から構成されます。プログラムがインタプリタに入力として読み込まれると、パーサーによって処理されます。パーサーはプログラムを言語コンポーネントに分解して構文木を形成します。評価器は構文木を使用してプログラムを実行します。[ 14 ]
仮想マシンは、バイトコードを解釈する特殊なタイプのインタプリタです。[ 9 ]バイトコードは、機械語に似た移植可能な低レベルコードですが、一般的には物理マシンではなく仮想マシン上で実行されます。[ 15 ]効率を向上させるために、Java [ 15 ] 、Python [ 16 ]、C# [ 17 ]などの多くのプログラミング言語は、解釈される前にバイトコードにコンパイルされます。
仮想マシンの中には、バイトコードの実行効率を向上させるために、ジャストインタイム (JIT) コンパイラを搭載しているものがあります。仮想マシンがバイトコードを実行している間、JIT コンパイラはバイトコードの一部が繰り返し使用されると判断した場合、その特定の部分をマシンコードにコンパイルします。その後、JIT コンパイラはマシンコードをメモリに格納し、仮想マシンが使用できるようにします。JIT コンパイラは、コンパイル時間の長さと実行時間の速さのバランスを取ろうとします。[ 9 ]
コンパイラは、ある言語で書かれたプログラムを別の言語に変換します。ほとんどのコンパイラは、フロントエンド、オプティマイザ、バックエンドの 3 つの段階で構成されています。フロントエンドはプログラムを理解する役割を担います。プログラムが有効であることを確認し、コンパイラがプログラムを表現するために使用するデータ構造である中間表現に変換します。オプティマイザは、中間表現を改善して、コンパイラが最終的に生成する実行可能ファイルの速度を向上させたり、サイズを小さくしたりします。バックエンドは、最適化された中間表現をコンパイラの出力言語に変換します。[ 18 ]
ある高水準言語のコンパイラが別の高水準言語を生成する場合、それはトランスパイラと呼ばれます。トランスパイラは、既存の言語を拡張したり、他の言語( Cなど)の移植性が高く最適化された実装を利用することでコンパイラ開発を簡素化したりするために使用できます。[ 9 ]
解釈とコンパイルの組み合わせは数多くあり、現代のプログラミング言語の実装の多くは両方の要素を含んでいます。たとえば、Smalltalkプログラミング言語は、従来はバイトコードへのコンパイルによって実装され、その後、仮想マシンによって解釈またはコンパイルされます。Smalltalk バイトコードは仮想マシン上で実行されるため、異なるハードウェア プラットフォーム間で移植可能です。[ 19 ]
プログラミング言語には複数の実装が存在する場合があります。異なる実装は異なる言語で記述され、コードのコンパイルや解釈に異なる方法を使用する場合があります。たとえば、Pythonの実装には次のものがあります。[ 20 ]
プログラミング言語を作成するプロセスは開発者によって異なる場合がありますが、ここでは一般的な概念を含む、プログラミング言語を作成する一般的なプロセスを示します。
入門編では、コンパイラ構築でおなじみの鶏と卵の問題が提示されます。つまり、コンパイラをブートストラップするにはコンパイラが必要であり、コンパイラ生成器のブートストラップも例外ではありません。