プログラミングの世界において、ソースコードが正しく記述されているかを判断するための基準は非常に重要です。

コンピューターが人間界の言語に近いプログラミング言語を理解するためには、その構造を厳密に定義する数学的な枠組みが必要となります。

その中心的な役割を担っているのが「文脈自由文法」と呼ばれる理論です。

本記事では、文脈自由文法の基本的な定義から、プログラミング言語の設計における具体的な役割、そして構文解析との深い関わりについて詳しく解説します。

文脈自由文法の基礎知識

文脈自由文法(Context-Free Grammar、CFG)とは、形式言語理論において特定の規則に従って文字列を生成するための仕組みの一種です。

この文法は、言語学者ノーム・チョムスキーによって提唱された「チョムスキー階層」の中で、タイプ2に分類される文法として知られています。

文脈自由という名称は、ある記号を別の記号に置き換える際に、その周囲の状況(文脈)に依存しないことに由来します。

例えば、ある変数 A を bc という文字列に置き換える規則がある場合、A がどのような文字列の中に現れても、常に bc に置き換えることが可能です。

この性質により、文法規則が非常にシンプルかつ強力なものとなり、コンピューターによる処理が容易になります。

現代のほとんどのプログラミング言語は、その構文(シンタックス)の大部分がこの文脈自由文法によって記述されています。

チョムスキー階層における位置付け

文脈自由文法を深く理解するためには、形式言語の分類体系であるチョムスキー階層の中での立ち位置を確認することが重要です。

チョムスキー階層は、文法の制約の強さに応じて4つの階層に分けられています。

階層(タイプ)文法の名称対応するオートマトン主な特徴
タイプ0無制約文法チューリングマシン制約が一切なく、あらゆる計算可能な言語を表現できる
タイプ1文脈依存文法線形拘束オートマトン置換の際に周囲の文脈を考慮する必要がある
タイプ2文脈自由文法プッシュダウン・オートマトンプログラミング言語の構文定義に最適である
タイプ3正規文法有限オートマトン最も制約が強く、正規表現で表される範囲

文脈自由文法は、正規文法よりも表現力が高く、文脈依存文法よりも解析が容易であるという絶妙なバランスを保っています。

正規文法では表現できない「括弧の対応関係」や「再帰的な構造」を記述できる点が、文脈自由文法の最大の特徴です。

文脈自由文法を構成する4つの要素

数学的な定義において、文脈自由文法は以下の4つの要素の組として定義されます。

非終端記号(Non-terminal symbols)

非終端記号は、さらに別の記号に置き換えることができる記号のことを指します。

通常は「文」や「式」、「項」といった、文法上の抽象的な構成単位を表すために用いられます。

最終的な文字列には現れず、生成の過程でのみ使用される仮の記号です。

終端記号(Terminal symbols)

終端記号は、それ以上分解したり置き換えたりすることができない、言語の最小単位となる記号です。

プログラミング言語においては、キーワード(if、while)、数値、演算子(+、-)、識別子などがこれに該当します。

最終的に生成されるソースコードの文字列は、すべてこの終端記号の並びで構成されます。

生成規則(Production rules)

生成規則は、どの非終端記号をどのような記号の列に書き換えるかを規定するルールです。

文脈自由文法では、左辺には必ず一つの非終端記号のみが記述されます。

右辺には、終端記号と非終端記号を自由に組み合わせた列を記述することができます。

開始記号(Start symbol)

開始記号は、文字列の生成をスタートさせる際の最初の非終端記号です。

すべての構文解析や文字列の生成プロセスは、この記号から出発します。

プログラミング言語における役割

文脈自由文法は、プログラミング言語の設計とコンパイラの開発において不可欠な技術です。

構文解析(パース)の仕組み

ソースコードがプログラムとして正しい構造を持っているかをチェックする工程を構文解析と呼びます。

コンパイラの中にある「構文解析器(パーサー)」は、文脈自由文法の規則に基づいてコードをスキャンします。

この過程で、コードの構造を木構造で表現した「構文木(パースツリー)」が作成されます。

もしソースコードが文法規則に従っていない場合、パーサーは構文エラー(Syntax Error)を出力して処理を中断します。

再帰的な構造の表現

文脈自由文法の強力な点は、自分自身を呼び出すような再帰的な定義が可能なことです。

例えば、数式における括弧の入れ子構造を考えてみましょう。

bnf
<expression> ::= <number>
               | "(" <expression> ")"
               | <expression> "+" <expression>

上記の例では、<expression> の定義の中に再び <expression> が含まれています。

これにより、((1 + 2) + 3) のような無限に続く入れ子構造を、簡潔なルールで定義することができます。

これは正規文法(正規表現)では実現不可能な、文脈自由文法ならではの強みです。

BNF記法による表現方法

文脈自由文法を具体的に記述するための表記法として、BNF(バッカス・ナウア記法)が広く使われています。

BNFは、メタ記号を用いてプログラミング言語の構文を厳密に定義するための言語です。

多くの言語仕様書において、このBNFまたはその拡張版であるEBNFが採用されています。

以下に、非常に単純な整数の加算を定義するBNFの例を示します。

bnf
<sum>         ::= <number> "+" <number>
<number>      ::= <digit> | <digit> <number>
<digit>       ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

この定義に従うと、「12+345」といった文字列が正しい構文として認められるようになります。

BNFを用いることで、開発者間で言語仕様の曖昧さを排除し、共通の理解を持つことが可能になります。

プッシュダウン・オートマトンとの関係

文脈自由文法によって定義される言語を認識する理論上の計算モデルを、プッシュダウン・オートマトン(PDA)と呼びます。

プッシュダウン・オートマトンは、通常の有限オートマトンに「スタック」というメモリ構造を追加したものです。

スタックを利用することで、それまでに読み込んだ記号の情報を一時的に保存し、後で取り出すことができます。

この「スタックに積む」という動作が、括弧の対応や関数の呼び出し関係を管理する上で決定的な役割を果たします。

コンパイラが構文解析を行う際、内部的にはこのプッシュダウン・オートマトンの原理が応用されています。

文脈自由文法の限界と解決策

文脈自由文法は万能ではありませんが、多くのプログラミング言語の記述には十分な能力を持っています。

しかし、厳密には「変数が使用される前に宣言されているか」といったチェックは、文脈自由文法の範囲外となります。

このような「意味論的」な制約は、文法チェックの後のフェーズである「静的解析」や「意味解析」によって補完されます。

また、構文解析のアルゴリズムには「LL法」や「LR法」といった種類があり、文法の書き方によっては解析が困難になる場合もあります。

特に、一つの文字列に対して複数の構文木が作れてしまう状態を「曖昧な文法」と呼び、これを回避するための工夫が言語設計者に求められます。

まとめ

文脈自由文法は、プログラミング言語の骨組みを支える理論的基盤であり、現代のコンピューティングに欠かせない要素です。

非終端記号や生成規則といったシンプルな仕組みを組み合わせることで、複雑なソースコードの構造を厳密に定義できます。

チョムスキー階層において、正規文法を超える表現力を持ちながらも、効率的な解析が可能であるという特性が、実用的な言語設計において高く評価されています。

BNF記法や構文解析の概念を学ぶことは、より高度なプログラミングやコンパイラの仕組みを理解するための第一歩となります。

私たちが毎日書いているコードの裏側には、このような数学的な美しさと論理的な整合性が潜んでいるのです。