Java目線でコンパイラの仕組みをのぞいてみよう!

Javaでコンパイラの基礎を理解する (6)

もしも、コンパイラ専門書が読めたなら……


ガリレオ
小山博史
2007/7/11


今回の主な内容

「構文解析」とは、いったい何なのか?
構文木を実現するクラス
いよいよ大詰め! 「構文解析」を行う
最後の仕上げ、「コード生成」を知る!
そして、Javaでコンパイラを実行する。
もしも、コンパイラ専門書が読めたなら……

 前回字句解析について解説し、字句解析プログラムをいくつか作成しました。今回は、字句解析の結果取得できる字句リストを解析して構文木を生成する構文解析と、その結果を使ってオブジェクトコードを生成する方法について解説をします。

 「構文解析」とは、いったい何なのか?

 連載第4回で、プログラミング言語S1sはBNFを使って次のように定義しました。

<program> ::= main '{' <expression> '}'
<expression> ::= <term>{ <opeas> <term> }
<term> ::= <factor>{ <opemd> <factor> }
<factor> ::= <number>|( <expression> )
<number> ::= <digit>{<digit>}
<opeas> ::= + | -
<opemd> ::= * | /
<digit> ::= 0|1|2|3|4|5|6|7|8|9

 構文解析では、与えられたプログラムのソースコードが、S1sのBNFから生成できるかどうかをチェックし、生成できる場合には構文木を生成します。

図1 構文解析の位置付け
図1 構文解析の位置付け

構文木とは?

 生成する構文木は言葉のとおり、木構造のデータになります。連載第2回のコラムで算術式木構造について説明をしましたが、同じようなものです。

 例えば、それぞれ図1〜3のような構文木となります。文法構造は、このように木構造で表しておくことがよくあります。

図2 main { 1+2 }の構文木
図2 main { 1+2 }の構文木

図3 main { 1+2*(3+4)*5 }の構文木
図3 main { 1+2*(3+4)*5 }の構文木

図4 main { ((1+2)*3+4)*5 }の構文木
図4 main { ((1+2)*3+4)*5 }の構文木

 構文解析によって、字句のリスト構造から、構文を表現する木構造のデータへ変換する必要があることは分かったと思うのですが、具体的にはどのような処理を書けばいいのでしょう?

 この処理を実現する構文解析法にはいくつか種類があるのですが、ここでは「下向き構文解析法」という方法を使うことにします。

注意!

なお、変数などを使えるように文法を定義している場合は、このときに名前表というものも生成するのですが、本連載では短い時間でコンパイラの基本的な原理を理解してもらうために、それらの処理については言及しません。ほかにも細かい話はいくつかありますが、それらについては次のステップとし、コンパイラ専門書に譲ることにします。

下向き構文解析法とは?

 では、「下向き構文解析法」の説明に入ります。この方法は、与えられた字句リストがBNFで定義されている文法と一致していると仮定し、開始記号から生成される記号列のパターンと照らし合わせながらチェックと構文木の作成をします。説明では分かりにくいのですが、プログラムの流れを見ると、簡単だということが分かるはずです。

 与えられた字句リストtokensが最初の生成規則である「 <program> ::= main '{' <expression> '}'」の文法を満たしているかを確認するためには、次のような処理の流れとなります。流れ図として表現すると分かりますが、文法で表現されている記号列について、終端記号はそのまま単純にチェックし、非終端記号については同様にしてチェック用の処理を用意すればよいということが分かります。

図5 <program>チェックの流れ図
図5 <program>チェックの流れ図

 拍子抜けするほど簡単に感じた読者も多いと思いますが、BNFで文法を表現したり、字句解析で字句リストを準備しておくことで、構文解析の処理を簡単に実現するための下準備をしていた点を忘れないでください。それらの準備があったからこそ、これからの処理が簡単になるのです。

 記号列が字句に分解されていない場合は、区切り文字などについても考慮した処理が必要になりますが、それらは字句解析の時点で行われているので、構文解析では考慮する必要がありません。

バックトラッキングとは?

 このように、構文解析の処理は文法の生成規則に従って処理を書くだけなので、簡単に実現できますが、実際に各生成規則について確認してみるといくつかの注意事項が出てきます。

 中でも重要な注意事項としては、一度読み込んだ字句について、読み込まなかったことにして処理を続ける場面が出てくるというものがあります。このような後戻り処理のことをバックトラッキングといいますが、次に読み込む字句が何かによって処理の流れが変わることがあるため、この処理が必要なのです。

 バックトラッキングの必要性については、「<program> ::= main { <expression> }」をチェックするプログラムの処理の流れだけでは分かりにくいのですが、「残りの字句リストが<expression>から生成できる記号列かチェック」するという処理についてちょっと考えてみると、分かります。

 この処理では、内部で最後の字句「}」を読み込んだ時点で処理を終了しますが、読み込んだ字句をそのままにしておくと、「<program> ::= main { <expression> }」をチェックするプログラムで利用できません。ですから、「残りの字句リストが<expression>から生成できる記号列かチェック」するという処理では、最後の字句「}」については終了判定のために読み込みますが、処理の終了時には読み込まなかった状態にして終了する必要があるのです。

 実際の構文解析プログラムを紹介する前に重要な点について説明をしましたが、分かりにくい点も多いと思います。実際のコードを見ると、分かってくると思いますから、そこで疑問点は解消するということでも構わないと思います。

1/4

Index
第6回 もしも、コンパイラ専門書が読めたなら……
Page1
「構文解析」とは、いったい何なのか?
  構文木とは?
  下向き構文解析法とは?
  バックトラッキングとは?
  Page2
構文木を実現するクラス
  “葉”のクラス
  “節”のクラス
  演算の優先度を理解する
  共通のスーパークラス:AbstractExpressionクラス
  プログラムを表現するノード:Programクラス
  演算を表す:Expressionクラス、Termクラス
  数字を表す:NumberExpressionクラス
  Page3
いよいよ大詰め! 「構文解析」を行う
  構文解析を行う:Parserクラス
  構文解析法の開始:createProgramメソッド
  加減演算子が出るまで処理:createExpressionメソッド
  乗除演算子が出るまで処理:createTermメソッド
  字句によって処理を変える:createFactorメソッド
  数値のインスタンスを作成:createNumberメソッド
  どの字句かの判定を行う:checkメソッド
  Page4
最後の仕上げ、 「コード生成」を知る!
  子要素が正しいかチェック:Programクラスのcompileメソッド
  加減演算子命令の生成:Expressionクラスのcompileメソッド
  乗除演算子命令の生成:Termクラスのcompileメソッド
  データ操作命令の生成:NumberExpressionクラスのcompileメソッド
  コンパイルを実際に実行するクラス:Compilerクラス
  起動を行うクラス:Svm1cクラス
そして、Javaでコンパイラを実行する。
もしも、コンパイラ専門書が読めたなら……






Java Solution全記事一覧



TechTargetジャパン

Java Solution フォーラム 新着記事

@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

RSSフィード

キャリアアップ

- PR -
@IT Sepcial

イベントカレンダー

PickUpイベント

- PR -
もっと見る
- PR -

お勧め求人情報

ホワイトペーパーTechTargetジャパン

@IT Sepcial
ソリューションFLASH