文脈自由文法は、正規表現よりも厳密に強力でありながら、マシンで簡単に処理できる文字列のセットの記述です。より正式には、文脈自由文法は4つのもので構成されます。
生成される文字列の要素である終端記号のセット。バイソンパーサーの場合、これは通常、スキャナーによって生成されたトークンのセットです。英語のような自然言語の文法の場合、これはすべての英語の単語のセットである可能性があります。
非終端記号のセット。直感的には、非終端記号は、「名詞」や「動詞」などの品詞のようなものを表します。
プロダクションのセット。各プロダクションは、非終端記号を他の終端記号と非終端記号のセットに置き換える方法を示しています。たとえば、プロダクションVariable -> Type Name
では、非終端記号が表示された場合Variable
、それを文字列に置き換えることができるとしていますType Name
。
派生が開始する非終端記号である開始記号。
例として、Cスタイルの関数宣言用のこの単純な文脈自由文法を考えてみましょう。
Function -> Type ident(Arguments)
Type -> int
Type -> Type *
Arguments -> e (the empty string)
Arguments -> ArgList
ArgList -> Type ident
ArgList -> Type ident, ArgList
ここで、開始記号はFunction
です。この文法を前提として、非終端記号を繰り返し選択し、一致する生成の右側の1つに置き換えることで、Cスタイルの関数宣言を生成できます。各ステップで、これまでに作成した文字列はセンテンスフォームと呼ばれます。たとえば、上記の文法から導き出すことができるいくつかの異なる関数宣言を次に示します。
Sentential Form Production
-------------------------------------------------------------------
Function Function -> Type ident(Arguments)
Type ident(Arguments) Type -> int
int ident(Arguments) Arguments -> e
int ident()
Sentential Form Production
-------------------------------------------------------------------
Function Function -> Type ident(Arguments)
Type ident(Arguments) Type -> Type*
Type* ident(Arguments) Type -> int
int* ident(Arguments) Arguments -> ArgList
int* ident(ArgList) ArgList -> Type ident, ArgList
int* ident(Type ident, ArgList) ArgList -> Type ident
int* ident(Type ident, Type ident) Type -> Type*
int* ident(Type* ident, Type ident) Type -> Type*
int* ident(Type** ident, Type ident) Type -> int
int* ident(int** ident, Type ident) Type -> int
int* ident(int** ident, int ident)
ほとんどのプログラミング言語は、文脈自由文法で記述できる構造を持っています。パーサーの仕事は、プログラムと文法を取り、そのプログラムが文法によってどのように生成されるかを決定することです。
LALR(1)に関しては、残念ながらLALR(1)の正式な定義は簡単ではありません。コンパイラー構築コースの指導を終えたばかりで、LALR(1)について話すことができたのは、最初に関連する構文解析手法について2つの講義を行った後です。資料の正式な紹介が必要な場合は、ボトムアップ構文解析に関する私のスライドをコースのWebサイトで入手できます。
LALR(1)は、ボトムアップ構文解析アルゴリズムと呼ばれる構文解析アルゴリズムの一種です。これは、プログラムを開始記号に戻すために、文法の生成を逆の順序で適用しようとすることを意味します。たとえば、上記の文法によって生成されたこの文字列について考えてみましょう。
int** ident(int* ident)
ボトムアップ解析では、プログラムを一度に1トークンずつ調べて、この文字列を解析します。非終端記号に戻すことができるものを見つけたときはいつでも、そうします。(より正確には、LALR(1)は、アルゴリズムがより多くのコンテキストを持つように、他の基準も満たされている場合にのみこの種の削減を行いますが、この例では、これについて心配する必要はありません)。解析の各ステップは、シフトまたはリデュースのいずれかであると言われます。シフトとは、入力のもう1つのトークンを調べて、適用する削減に関する詳細情報を取得することを意味します。削減とは、いくつかのトークンと非終端記号を取得し、生成を逆にして非終端記号に戻すことを意味します。
文字列のボトムアップ解析のトレースは次のとおりです。
Workspace Input Action
-----------------------------------------------------------------
int** ident(int* ident) Shift
int ** ident(int* ident) Reduce Type -> int
Type ** ident(int* ident) Shift
Type* * ident(int* ident) Reduce Type -> Type*
Type * ident(int* ident) Shift
Type* ident(int* ident) Reduce Type -> Type*
Type ident(int* ident) Shift
Type ident (int* ident) Shift
Type ident( int* ident) Shift
Type ident(int * ident) Reduce Type -> int
Type ident(Type * ident) Shift
Type ident(Type* ident) Reduce Type -> Type*
Type ident(Type ident) Shift
Type ident(Type ident ) Reduce ArgList -> Type ident
Type ident(ArgList ) Reduce Arguments -> ArgList
Type ident(Arguments ) Shift
Type ident(Arguments) Reduce Function -> Type ident(Arguments)
Function ACCEPT
バイソンを使用すると、シフト/削減の競合が不変に発生し、競合が削減/削減されるため、シフトと削減について知っておくことが重要です。これらのエラーは、パーサージェネレーターが、パーサーがシフトするか削減するか、または2つの削減のどちらを実行するかを判断できない状態になる可能性があると判断したことを意味します。これに対処する方法の詳細については、バイソンのマニュアルを参照してください。
文脈自由文法と構文解析アルゴリズム全般について詳しく知りたい場合は、 Grune and Jacobsによる「 ParsingTechniques:A Practical Guide、Second Edition」というタイトルの優れた本があります。これは、資料の最高の扱いを示しています。私が今まで見てきました。これは、より広く使用され始めているLALR(1)よりも大幅に強力な多くの手法(GLRやEarleyなど)を含む、あらゆる種類の解析アルゴリズムをカバーしています。私はこの本を強くお勧めします-それが私が構文解析をとてもよく理解している主な理由です!
お役に立てれば!