コンパイラ/コンパイラの概説
コンパイラの定義は、狭義では「ある言語から別の言語へ変換するプログラム」である。この変換元の言語を「原始言語」と呼び、変換後の言語を「目的言語」と呼ぶ。この際、原始言語に誤りがあると、コンパイラはそれを通知する。
広義では「形式言語で書かれたプログラムを実行するプログラム」である。
コンパイラではいくつかのフェーズに分かれており、字句解析に始まり、構文解析、意味解析、目的言語のコード生成、というのが一般的な流れである。途中で中間言語の生成や、最適化などが行われることもある。解析する言語によっては、字句解析フェーズと構文解析フェーズ、意味解析フェーズのどれかが結びついていることもあり、完全に分離することは出来ない場合もある。C++言語のの短いコードを例に取る[1]。
X * y;
このコードは、「Xという構造体(共用体など)のポインタyを宣言」しているのか「X かける yを計算している」のかが曖昧である(詳細)。そのため、構文解析だけではこの曖昧さが残ってしまうので、意味解析と構文解析が一部結合していると言える。
コンパイラの設計や保守に関わる人はあまりいないだろうが、コンパイラの構造や設計、考え方を理解することはソフトウェア開発やプログラミング全般に非常に役立つであろう。
また、正規表現などもプログラムには広く使われている。
字句解析[編集]
コンパイラがまず対象とするプログラムを読み込んだら、次に行うことが字句解析である。字句解析はプログラムを左から右へ線形解析を行う。これを字句解析または走査と呼ぶ。
字句解析というのは、例えば、a = b * c + d
というコードを
- a
- =
- b
- *
- c
- +
- d
に分けることである。このとき分けるのはプログラムのソースコードの最小単位の単語であり、これをトークンと呼ぶ。また、トークン同士の区切りである空白やコメント類は字句解析で取り除かれる。
構文解析[編集]
構文解析は、解析木もしくは構文木を作る。これを作らなければ構文解析の正当性は評価できない。
a = b * c + d
の構文木は以下の通りである。
ここで、b*c
とd
を+が持っているのはかけ算が足し算よりも優先されるからである。詳しくは、演算子の優先順位を参照のこと。
構文木と解析木の違いは、構文木は終端記号が親となっているのに対し、解析木は非終端記号が親となっているという点である。「親」というのは木の末節ではなく、下にも何かを持つもののことを指す。解析木は構文解析を行ったものをそのままなぞるだけなので作るのは簡単であるが、構文木に比べサイズが大きく煩雑である。構文木はそれを生成する方法を別途で考えないといけないが解析木に比べ簡単である。
意味解析[編集]
意味解析では、プログラムが意味的に誤りを犯していないかということを検査する。例えば型に関して言えば、型検査、整数型の変数に実数の値を代入していないかといったことや、型の推測を行ったりする。
また、C言語などの言語において、ループ外でbreakやcontinueといった文が無いか、重複して変数や識別子を宣言していないか、といった、構文的には何ら誤りはないが意味的には誤りということを検知するのに大きな役割を果たす。
構文解析でできた構文木または解析木にプロパティをつけ、コード生成に向けた動作を行うのが、この意味解析のフェーズである。