1. 実験の目的
構文解析はプログラムのコンパイルの中核部分です。この実験では、典型的なトップダウン解析プログラムである LL(1) 解析プログラムを設計することで、解析の原理と実装テクニックをさらに理解し、習得することができます。
2. トピックの理解と説明
文法解析の主な仕事は、「単語を文にまとめる」ことであり、字句解析によって得られた単語列を、「プログラム、文章、表現」などの文法規則に従って、より大きな文法単位にまとめるということです。文法解析の機能は、指定された入力文字列が文法的な文であるかどうかを判断するために使用します。
構文ツリーを生成するさまざまな方向に応じて、一般的に使用される 2 つの構文解析方法があります。トップダウン解析とボトムアップ解析です。トップダウン分析は、目標指向分析手法とも呼ばれ、文法の先頭から入力単語列に一致する文を導き出そうとします。ボトムアップ分析はシフトリデュース分析法とも呼ばれ、入力された単語列から開始して文法の開始記号まで還元しようとします。
予測分析手法 (LL(1) 手法) の基本的な考え方は、文法開始記号 S から開始し、ソース プログラムを左から右に走査し、一度に 1 文字ずつ前方を見て、適切な生成式を選択し、そして最も左の派生を生成します。私が作成した文法アナライザーは C++ 言語に基づいています。開発環境はvs2017です。
3. プログラムの機能と枠組み
1.プログラム機能
(1) プログラムの主な機能は次のとおりです。
(2) 入力終端記号セット、非終端記号セット、および文法生成セットが実現され、保存されます。
(3) FIRST 集合と FOLLOW 集合を自動的に解きます。
(4)FIRST集合とFOLLOW集合に基づく予測分析テーブルMの自動構築を実現する。
(5) 正しい文字列の解析・認識を実現し、認識過程を記録し、最終的に画面に表示します。
(6) 間違った文字列のスキップ認識を実現し、認識過程を記録し、最終的に画面に表示します。
2. プログラムの枠組み
プログラムは主に 3 つのモジュールで構成されます。これら 3 つの主要なブロックは次のとおりです。
(1) データ記憶構造モジュール データ記憶構造モジュールは主に、終端記号セット、非終端記号セット、文法生成セット、入力テスト文字列などの必要な入力データの記憶方法を確立します。
同時に、FIRST セット、FOLLOW セット、予測分析テーブル、予測分析スタック、および関連するセット要素番号レコード変数の格納タイプと方法がさらに確立されます。
(2) 補助機能モジュール 補助機能モジュールは、主にコア機能モジュールのアルゴリズムを実現しやすくすることを目的としており、いくつかの補助機能関数が設計されています。
これには主に以下が含まれます: 複数のオプションで生成を分解するサブルーチン (例: A->bc|RD)、文法テキスト入力のサブルーチン、特定の記号要素が対応する集合に含まれるかどうかを判断するサブルーチン、集合加算サブルーチンシンボル要素が返される記憶構造に添字を付けるサブルーチンと、セットの繰り返し要素を削除するサブルーチンです。
(3) コア機能モジュール コア機能モジュールには、FIRST 集合解決サブルーチン、FOLLOW 集合解決サブルーチン、予測分析表解決サブルーチン、文法分析サブルーチンの 4 つのサブルーチンのみが含まれます。
これら 4 種類の関数によって完了する機能は、FIRST 集合の解決、FOLLOW 集合の解決、予測分析テーブルの構築、入力文字列の識別と分析、分析手順と分析結果の入力です。
4. 設計プロセス
上記のプログラムフレームワーク分析によれば、構文解析器の設計プロセスは 3 つの主要なプロセスに分かれていることが容易にわかります。データ構造設計、補助機能モジュール設計、コアアルゴリズム設計です。
1. データ構造の設計
データ構造の設計は主に、必要な入力データと関連パラメータの格納タイプと格納方法を設計することです。解決すべき問題には主に 3 つのタイプがあります: 入力データ、解データ、関連パラメーターです。
(1) 考慮される入力データは、終端記号セット、非終端記号セット、文法生成セット、テスト文字列です。
(2) 考慮する必要がある関連ソリューション データは、非終端記号の最初のセット、非終端記号の次のセット、予測分析テーブル、および分析スタックです。
(3) 考慮する必要がある関連パラメータ: 終端記号セット内の要素の数、非終端記号内の要素の数、入力文法生成の数、および分解された生成の数。
最初のタイプの問題では、テスト文字列の記憶構造は、容量 100 で初期状態は空のグローバル変数の文字配列として設計されます。終端記号セットと非終端記号セットは両方ともグローバル変数の文字配列であり、容量は 100 で、最初は空のストレージ設計です。文法生成セットは、100×100 の容量を持つ 2 次元文字配列グローバル変数として設計されており、最初は空です。
具体的には次のように設計されています。
char word[100] = {
"" };//待测试的文字
char Vt[100] = {
"" };//终结符
char Vn[100] = {
"" };//非终结符
string Generative[100] = {
"" };//文法产生式存储
2 番目のタイプの問題では、非終端シンボルの FIRST セットと非終端シンボルの FOLLOW セットは、容量が 100×100 で初期値が空の 2 次元配列変数として設計されます。分析スタックは、文字 (char) 型のスタック グローバル変数として設計されています。予測分析テーブルは、100×100 の容量を持つ 2 次元の整数配列として設計されており、すべての初期化は -1 です。
予測分析テーブルの説明: 予測分析テーブルがこのような構造になっているのは、その後のアルゴリズムを容易にするためです。2 次元整数配列の各 [i][j] 要素には、i 番目の非終端記号と j 番目の終端記号に対応する生成記憶場所の添字が格納されます。このうち、i は非終端記号配列の対応する要素の添字であり、j も同じです。
具体的には次のように設計されています。
string first[100] = {
"" };//first集合
string follow[100] = {
"" };//follow集合
stack<char> st;//预测分析栈
int table[100][100] = {
0 };//预测表针
3 番目のタイプの問題では、すべてのパラメーターがグローバル変数の整数変数を使用して保存されます。主な目的は、次のアルゴリズムのループを容易にすることです。
具体的なデザイン:
int VtNum = 0;//终结符号的个数
int VnNum = 0;//非终结符号的个数
int GenNum = 0;//文法产生式个数
int GenNumNew = 0;//文法产生式分解后的个数
2. 補助機能モジュールの設計
補助機能モジュールの設計は、この実験プログラム設計の焦点ではありません。このプログラム設計の焦点は、次のコア アルゴリズムの設計です。したがって、このセクションでは簡単にのみ言及します。
このセクションには主に 6 つの主要な機能が含まれています。それらはそれぞれ、複数のオプションを持つプロダクションを分解するためのサブルーチン (例: A->bc|RD)、文法テキスト入力のためのサブルーチン、特定の記号要素が対応するセットに含まれるかどうかを判断するためのサブルーチン、セットを追加するためのサブルーチンです。シンボル要素が返される記憶構造に添字を付けるサブルーチンと、セットの繰り返し要素を削除するサブルーチンです。
(1) 複数のオプションを使用してプロダクションを分解する (例: A->bc|RD) サブルーチン
このプログラムは主に、後から予測分析テーブルを構築する際の利便性を目的として設計されています。主な機能は、変数 Generative 内の元の文法生成入力を分解することです。原始的な文法生成には、C->+BC|@ などの多肢選択生成が含まれる場合があります。このサブルーチンは、そのようなプロダクションを 2 つのプロダクション (C->+BC、C->@) に分解します。そして、分解された文法生成の拡張セットを新しい変数 GenerativeNew に保存します。この変数は依然として 100×100 の容量を持つ 2 次元の文字配列グローバル変数であり、最初は空です。
その具体的な設計は次のとおりです。
string GenerativeNew[100] = {
"" };//文法产生式分解后的存储
このサブルーチンは次の目的で設計されています。
void GramF();//分解产生式程序
具体的な実装は比較的単純です。詳細についてはソース コードを参照してください。
(2) 文法テキスト入力サブルーチン
サブルーチンは、文法入力とテキスト入力の 2 つの部分に分かれています。テキスト入力とは文字列を入力することを指しますが、これは簡単で紹介する必要はありません。文法入力には、終端記号セット、非終端記号セット、および文法生成入力の 3 つの部分が含まれます。最初の 2 つの部分は入力文字列で、後半の部分は n 個を超える文字列であり、「end」で終わります。
注: 入力する終端記号はすべてである必要があり、「#」を入力する必要はありません。入力非終端は開始文字で始まる必要があります。このパーサーは、「@」記号を null 記号として扱います。
具体的なデザイン:
void GramIn();//文法输入程序
void WordIn();//文字输入程序
詳細な実装についてはソース コードを参照してください。
(3) シンボル要素が対応する集合にあるかどうかを判定するサブルーチン
この部分は、ある記号要素が終端記号集合にあるか非終端記号集合にあるかを判断する機能と、ある記号が FIRST 集合にあるか FOLLOW 集合にあるかを判断する機能に分かれています。
前者の入力パラメータは、特定の文字(char)、判定対象の文字セット(char[])、文字セットの容量(int)です。
出力結果: 「はい」または「いいえ」。
具体的なデザイン:
bool Is(char x, char a[], int n); //是否在集合中
後者の入力パラメータは、特定の文字 (char)、判定対象の文字のセット (char []) で、出力結果は、yes または no です。
bool IsChar(char a, string b);//判断一个字符是否在一个集合中的程序
詳細な実装についてはソース コードを参照してください。
(4) 加算設定サブルーチン
集合加算は主に FIRST 集合と FOLLOW 集合を解く過程で使用されます。入力: 拡張される文字セット (string&)、追加される文字セット (string&)
出力: なし
具体的な設計は次のとおりです。
void ADD(string& a, string& b);//集合相加程序
void ADDfollow(string& a, string& b);//集合(follow)相加程序
上記 2 つの関数の違いは、前者は「@」ヌル文字を削除する必要があることです。後者は必要ありません。詳細についてはソースコードを参照してください。
(5) シンボル要素に従って、それが配置されている記憶構造のサブルーチンを返します。
このサブルーチンには多くの用途があります。予測分析テーブルの構築、文字列認識プログラムの分析、FIRST セットと FOLLOW セットの解決などのアルゴリズム機能に関与します。
入力: シンボル要素 (char)
出力: 要素が位置する村の大まかな構造位置の添字
具体的なデザインは次のとおりです。
int Back(char a);//根据字母返回下标程序
詳細についてはソースコードを参照してください。
(6) セットから重複する要素を削除するサブルーチン
FIRST セットと FOLLOW セットを解いた後、後で予測分析テーブルを正しく解くための基本的な作業を行うために、対応するセットの繰り返し要素を削除して整理する必要があります。
入力: なし
出力: なし。
この関数は、FISRT セットと FOLLOW セットを直接編成します。
具体的には次のように設計されています。
void clearF();//净化程序
詳細についてはソースコードを参照してください。
3. コアアルゴリズムの設計
コアアルゴリズムは主に、FIRST 集合解決サブルーチン、FOLLOW 集合解決サブルーチン、予測分析テーブル解決サブルーチン、文法分析サブルーチンの 4 つの部分で構成されます。この部分は構文アナライザー全体の中核となる部分なので、ここでは重点的に説明します。
(1) FIRST 集合解決サブルーチン
FIRST 集合解決は主に次の原則に基づいています。
/************first集求解程序*************/
void GetFIRST(char a) {
int i, k = 0;
for (i = 0; i < GenNumNew; i++) {
if (GenerativeNew[i][0] != a)
continue;
if (Is(GenerativeNew[i][3], Vt, VtNum)) {
//如果该非终结符产生式右部第一个字符是终结符号
//则直接将其计入左部非终结符的FIRST集
first[Back(GenerativeNew[i][0])] += GenerativeNew[i][3];
}
else if (Is(GenerativeNew[i][3], Vn, VnNum)) {
//如果该非终结符号右部第一个字符是非终结符号
//则对该右部第一个字符的FIRST进行求解
//并将其加入左部字符的FIRST集
GetFIRST(GenerativeNew[i][3]);
ADD(first[Back(GenerativeNew[i][0])], first[Back(GenerativeNew[i][3])]);
}
else if (GenerativeNew[i][3] == '@') {
//如果该非终结符产生式是个空
//则将空加入左部字符的FIRST集
int j = 0;
while (first[Back(GenerativeNew[i][0])][j] != '\0') {
if (first[Back(GenerativeNew[i][0])][j] == '@') {
k = 1;
break;
}
j++;
}
if (!k)
first[Back(GenerativeNew[i][0])] += '@';
}
}
}
/************first集求解程序*************/
上記の関数設計からわかるように、これは再帰関数です。右側の最初の非終端の FIRST セットを解決するときにそれ自体が呼び出されます。i の機能は、分解された実動セットを走査することです。一部の非ターミナルには複数のオプションがあるため、分解後、キャラクターはさまざまなプロダクションに分散されます。たとえば、C->+BC|@ は C->+BC と C->@ に分解されます。C 文字を解く場合、上記 2 つの生成を別々に解く必要があります。したがって、それを横断する必要があります。
(2) FOLLOW 集合解決サブルーチン FOLLOW の原理は
次のとおりです:
文法 G の各非終端記号 A に対して FOLLOW(A) を構築する方法は、各 FOLLOW が増加しなくなるまで次の規則を継続的に使用することです。文法 開始記号 S、FOLLOW(S) に # を入れる; A->aBb がプロダクションの場合、FIRST(b){ε} を FOLLOW(B) に追加する; A->aB がプロダクションの場合、または A- >aBb は生成であり、b=>ε (つまり ε∈FIRST(b)) は FOLLOW(A) を FOLLOW(B) に追加します。
/************follow集求解程序*************/
void GetFOLLOW(char a) {
int nk;
nk = Back(a);
int i, j;
i = nk;
if (i == 0) {
//如果待求解字符是开始字符
//则把'#'加入其FOLLOW集
if (IsChar('#', follow[Back(a)]))
;
else
follow[Back(a)] += '#';
}
for (j = 0; j < GenNumNew; j++) {
if (GenerativeNew[j][3] == a && GenerativeNew[j][4] != '\0') {
//如果是A->Bb
if (Is(GenerativeNew[j][4], Vt, VtNum)) {
//如果b是终结符号,直接加入follow(B)
if (IsChar(GenerativeNew[j][4], follow[Back(a)]))
;
else
follow[Back(a)] += GenerativeNew[j][4];
}
else if (Is(GenerativeNew[j][4], Vn, VnNum)) {
//如果b是非终结符号,需要判断
if (IsChar('@', first[Back(GenerativeNew[j][4])])) {
//如果b可以推出空'@',则需要将follow(A)加入follow(B)
GetFOLLOW(GenerativeNew[j][0]);
ADDfollow(follow[Back(a)], follow[Back(GenerativeNew[j][0])]);
}
ADD(follow[Back(a)], first[Back(GenerativeNew[j][4])]);
}
}
else if (GenerativeNew[j][4] == a && GenerativeNew[j][5] != '\0') {
//如果是A->aBb
if (Is(GenerativeNew[j][5], Vt, VtNum)) {
//如果b是终结符号,直接加入follow(B)
if (IsChar(GenerativeNew[j][5], follow[Back(a)]))
;
else
follow[Back(a)] += GenerativeNew[j][5];
}
else if (Is(GenerativeNew[j][5], Vn, VnNum)) {
//如果b是非终结符号,需进行判断
if (IsChar('@', first[Back(GenerativeNew[j][5])])) {
//如果b可以推出空'@',则需要将follow(A)加入follow(B)
GetFOLLOW(GenerativeNew[j][0]);
ADDfollow(follow[Back(a)], follow[Back(GenerativeNew[j][0])]);
}
ADD(follow[Back(a)], first[Back(GenerativeNew[j][5])]);
}
}
else if (GenerativeNew[j][4] == a && GenerativeNew[j][5] == '\0') {
//如果是A->aB
GetFOLLOW(GenerativeNew[j][0]);//直接将follow(A)加入follow(B)
ADDfollow(follow[Back(a)], follow[Back(GenerativeNew[j][0])]);
}
}
}
/************follow集求解程序*************/
上記の入力を解決するプロセスでは、分解されたプロダクション セットをトラバースして解決する必要があります。これにより、すべての非終端記号が漏れなく解決されます。
(3) 予測分析テーブル解決サブルーチン
予測分析テーブルの解決原理:
/*
算法语言描述为:
对文法G的每个产生式A->a执行第二步和第三步;
对每个终结符a∈FIRST(a),把A->a加至M[A,a]中;
若ε∈FIRST(a),则把任何b∈FOLLOW(A)把A->a加至M[A,b]中;
把所有无定义的M[A,a]标上出错标志.
其算法流程图为:
*/
/************预测分析表构建程序*************/
void FAtable() {
int i, j, k;
for (i = 0; i < VtNum; i++) {
for (j = 0; j < GenNumNew; j++) {
if (Vt[i] == GenerativeNew[j][3])
//如果终结符Vt[i]在A->a的first(a)中,则将A->a放入table[A,Vt[i]]中
table[Back(GenerativeNew[j][0])][i] = j;
else if (Is(GenerativeNew[j][3], Vn, VnNum)) {
if (IsChar(Vt[i], first[Back(GenerativeNew[j][3])])) {
table[Back(GenerativeNew[j][0])][i] = j;
}
}
else if (GenerativeNew[j][3] == '@') {
//如果当前的产生式是:A->a且,a='@',则判断当前的Vt[i]是否在
if (IsChar(Vt[i], follow[Back(GenerativeNew[j][0])])) {
table[Back(GenerativeNew[j][0])][i] = j;
}
}
}
}
}
/************预测分析表构建程序*************/
(4) 文法解析サブルーチン
文法解析原理:
予測解析プログラムのマスター制御プログラムは、常に STACK スタックの先頭のシンボル X と現在の入力シンボルに従って動作します。マスター制御プログラムはすべて、次の 3 つの可能なアクションのいずれかを実行します。X=a=”#” の場合は、分析の成功を宣言し、分析プロセスを停止します。X=a≠”#” の場合、X はプログラムの先頭から追い出されます。 STACK スタックで、a が次の入力記号を指すようにします。X が非終端記号の場合、解析テーブル M を確認します。M[A, a] に X に関する生成がある場合、まず、X は次のとおりです。 STACK スタックの先頭から追い出され、プロダクションの右のシンボル文字列を逆の順序で 1 つずつ STACK スタックにプッシュします (右のシンボルが ε の場合、スタックには何もプッシュされないことを意味します)。生成の正しいシンボルをスタックにプッシュしながら実行されます。生成式に対応する意味アクションは、M[A, a]に「エラーフラグ」が格納されている場合、エラー診断プログラム ERROR が呼び出されます。
/************文法分析程序*************/
void GAnalysis() {
int i = 0, x, y, k,error=0,n=1;
char abc;
string chan = "";
st.push('#');
st.push(Vn[0]);
abc = st.top();
while (!(abc == word[i] && abc == '#')) {
if (Is(st.top(), Vn, VnNum)) {
x = Back(st.top());
y = BBack(word[i]);
k = table[x][y];//获得产生式
if (k == -1) {
error++;
cout << "步骤["<<n<<"]:识别错误!跳过"<<chan[i]<<";\n";
n++;
i++;
//break;
}
else {
chan = GenerativeNew[k];
k = 0;
st.pop();
while (chan[k] != '\0') {
k++;
}
k--;
if (chan[k] != '@') {
while (chan[k] != '>') {
st.push(chan[k]);
k--;
}
//i++;
cout << "步骤[" << n << "]:用" << chan << "的右部分逆序入栈已经完成;\n";
n++;
}
else {
cout << "步骤[" << n << "]:用" << chan << ";\n";
n++;
//i++;
}
}
}
else if (Is(st.top(), Vt, VtNum)) {
if (st.top() == word[i]) {
cout << "步骤[" << n << "]:匹配栈顶和当前符号" << word[i] << ",成功;\n";
st.pop();
i++;
n++;
}
else {
cout << "步骤[" << n << "]:识别失败!!\n";
n++;
break;
}
}
abc = st.top();
}
if (error) {
cout << "步骤[" << n << "]:识别错误!!错误跳过次数:"<<error<<"\n";
n++;
}
else {
cout << "步骤[" << n << "]:识别成功!!\n";
n++;
}
}
/************文法分析程序*************/
5. ユーザー操作ガイド
ユーザーは、対応する vs コンパイル環境または devc++ 環境でこのプログラムを実行できます。
ユーザーがこのプログラムを実行するときは、最初に終端記号のセットを入力し、次に非終端記号を入力する必要があります。さらに、非終端記号の最初の文字は文法の開始文字でなければなりません。最後に、文法生成を段階的に入力し、最後に入力終了マークとして「end」を使用します。
実行後、出力されたFIRSTセットとFOLLOWセットおよび予測分析テーブルが画面に表示されます。次に、ユーザーはテスト文字列を入力するように求められます。テスト文字列の最後の文字は「#」である必要があります。
7. テストデータと完全なプログラム
テスト データは次のとおりです。
終端文字: {i,+, , (,)}
非終端文字: {A,B,C,D,E}
文法生成:
A->BC
C->+BC|@
B-> DE
E-> DE|@
D->(A)|i
手動解決後の FIRST は次のとおりです:
FIRST(A):{(,i}
FIRST(B):{(,i}
FIRST©:{+,@}
FIRST( D): {(,i}
FIRST(E):{
,@}
手動解決後の FOLLOW セット:
FOLLOW(A): {#,)}
FOLLOW(B): {#,),+}
FOLLOW© : {# ,)}
FOLLOW(D): {#,),+, }
FOLLOW(E): {#,),+}
手動解決後の予測分析表
フルバージョンのソース:
#include "pch.h"
#include <iostream>
#include<stdio.h>
#include<string>
#include<stdlib.h>
#include<stack>
using namespace std;
void GramF();//分解产生式程序
void GramIn();//文法输入程序
void WordIn();//文字输入程序
void ClearGram();//清除文法程序
bool Is(char x, char a[], int n); //是否在集合中
void GetFIRST(char a);//first集求解程序
void ADD(string& a, string& b);//集合相加程序
void ADDfollow(string& a, string& b);//集合(follow)相加程序
int Back(char a);//根据字母返回下标程序
void GetFOLLOW(char a);//follow集求解程序
bool IsChar(char a, string b);//判断一个字符是否在一个集合中的程序
void FAtable();//预测分析表构建程序
void GAnalysis();//文法分析程序
void clearF();//净化程序
void cc();
int table[100][100] = {
0 };//预测表
char Vt[100] = {
"" };//终结符
char Vn[100] = {
"" };//非终结符
string Generative[100] = {
"" };//文法产生式存储
string first[100] = {
"" };//first集合
string follow[100] = {
"" };//follow集合
string GenerativeNew[100] = {
"" };//文法产生式分解后的存储
char word[100] = {
"" };//待测试的文字
int VtNum = 0;//终结符号的个数
int VnNum = 0;//非终结符号的个数
int GenNum = 0;//文法产生式个数
int GenNumNew = 0;//文法产生式分解后的个数
stack<char> st;//预测分析栈
void cc() {
int i, j, k, q, p;
char a;
for (i = 0; i < VnNum; i++) {
j = 0;
while (first[i][j] != '\0') {
k = j + 1;
while (first[i][k] != '\0') {
if (first[i][j] == first[i][k])
first[i][k] = ' ';
k++;
}
j++;
}
q = 0;
p = 0;
while (first[i][q] != '\0') {
if (first[i][q] != ' ') {
;
}
else {
p = q + 1;
while (first[i][p] != ' ') {
if (first[i][p] == '\0')
break;
else {
first[i][q] = first[i][p];
first[i][p] = ' ';
}
}
}
q++;
}
q = 0;
while (first[i][q] != '\0') {
if (first[i][q] == ' ')
first[i][q] = '\0';
q++;
}
}
}
/************分解产生式程序*************/
void GramF() {
int i, j, z = 0, x;
for (i = 0; i < GenNum; i++) {
x = 0;
while (1) {
char aa;
aa = Generative[i][x];
if (aa == '\0' || aa == '|')
break;
GenerativeNew[z] += Generative[i][x];
x++;
}
z++;
j = 0;
while (Generative[i][j] != '\0') {
if (Generative[i][j] == '|') {
j++;
for (x = 0; x < 3; x++) {
GenerativeNew[z] += Generative[i][x];
}
while (1) {
char a;
a = Generative[i][j];
if (a == '\0' || a == '|')
break;
//Generative[i][j] != '\0' || Generative[i][j] != '|'
GenerativeNew[z] += Generative[i][j];
j++;
}
z++;
}
else {
j++;
}
}
}
GenNumNew = z;
}
/************分解产生式程序*************/
/************文法输入程序*************/
void GramIn() {
int i = 0;
printf("请输入终结符号:\n");
scanf("%c", Vt + i);
while (*(Vt + i) != '\n') {
i++;
scanf("%c", Vt + i);
}
Vt[i] = '#';
i++;
VtNum = i;//输入结束存储终结符号个数
i = 0;//初始化i值准备输入非终结符号
printf("请输入非终结符号:\n");
scanf("%c", Vn + i);
while (*(Vn + i) != '\n') {
i++;
scanf("%c", Vn + i);
}
VnNum = i;//输入结束存储非终结符号个数
i = 0;//初始化i值准备输入文法产生式
printf("请输入文法产生式:\n");
while (cin >> Generative[i] && Generative[i] != "end") {
i++;
}
GenNum = i;//输入结束存储非终结符号个数
}
/************文法输入程序*************/
/************文字输入程序*************/
void WordIn() {
printf("请输入您需要测试的文字:\n");
int i = 0;
getchar();
scanf("%c", word + i);
while (*(word + i) != '\n') {
i++;
scanf("%c", word + i);
}
}
/************文字输入程序*************/
/************清除文法程序*************/
void ClearGram() {
table[100][100] = {
0 };//预测表
Generative[100] = {
"" };//文法产生式存储
first[100] = {
"" };//first集合
follow[100] = {
"" };//follow集合
GenerativeNew[100] = {
"" };//文法产生式分解后的存储
VtNum = 0;//终结符号的个数
VnNum = 0;//非终结符号的个数
GenNum = 0;//文法产生式个数
GenNumNew = 0;//文法产生式分解后的个数
for (int i = 0; i < 100; i++) {
Vt[i] = '\0';//终结符
Vn[i] = '\0';//非终结符
word[i] = '\0';//待测试的文字
}
}
/************清除文法程序*************/
/************是否在集合中*************/
bool Is(char x, char a[], int n) {
for (int i = 0; i < n; i++) {
if (a[i] == x)
return true;
}
return false;
}
/************是否在集合中*************/
/************first集求解程序*************/
void GetFIRST(char a) {
int i, k = 0;
for (i = 0; i < GenNumNew; i++) {
if (GenerativeNew[i][0] != a)
continue;
if (Is(GenerativeNew[i][3], Vt, VtNum)) {
//如果该非终结符产生式右部第一个字符是终结符号
//则直接将其计入左部非终结符的FIRST集
first[Back(GenerativeNew[i][0])] += GenerativeNew[i][3];
}
else if (Is(GenerativeNew[i][3], Vn, VnNum)) {
//如果该非终结符号右部第一个字符是非终结符号
//则对该右部第一个字符的FIRST进行求解
//并将其加入左部字符的FIRST集
GetFIRST(GenerativeNew[i][3]);
ADD(first[Back(GenerativeNew[i][0])], first[Back(GenerativeNew[i][3])]);
}
else if (GenerativeNew[i][3] == '@') {
//如果该非终结符产生式是个空
//则将空加入左部字符的FIRST集
int j = 0;
while (first[Back(GenerativeNew[i][0])][j] != '\0') {
if (first[Back(GenerativeNew[i][0])][j] == '@') {
k = 1;
break;
}
j++;
}
if (!k)
first[Back(GenerativeNew[i][0])] += '@';
}
}
}
/************first集求解程序*************/
/************first集清除重复元素程序*************/
void clearF() {
int i, j, k, q, p;
char a;
//下面是清除follow集
for (i = 0; i < VnNum; i++) {
j = 0;
while (follow[i][j] != '\0') {
k = j + 1;
while (follow[i][k] != '\0') {
if (follow[i][j] == follow[i][k])
follow[i][k] = ' ';
k++;
}
j++;
}
q = 0;
p = 0;
while (follow[i][q] != '\0') {
if (follow[i][q] != ' ') {
;
}
else {
p = q + 1;
while (follow[i][p] != ' ') {
if (follow[i][p] == '\0')
break;
else {
follow[i][q] = follow[i][p];
follow[i][p] = ' ';
}
}
}
q++;
}
q = 0;
while (follow[i][q] != '\0') {
if (follow[i][q] == ' ')
follow[i][q] = '\0';
q++;
}
}
}
/************first集清除重复元素程序*************/
/************集合相加程序*************/
void ADD(string& a, string& b) {
int i = 0, zk = 1, j = 0;
while (b[j] != '\0') {
i = 0;
zk = 1;
while (a[i] != '\0') {
if (b[j] == a[i] || b[j] == '@') {
zk = -1;
break;
}
i++;
}
if (zk == 1)
a += b[j];
j++;
}
}
/************集合相加程序*************/
/************集合(follow)相加程序*************/
void ADDfollow(string& a, string& b) {
int i = 0, zk = 1, j = 0;
while (b[j] != '\0') {
i = 0;
zk = 1;
while (a[i] != '\0') {
if (b[j] == a[i]) {
zk = -1;
break;
}
i++;
}
if (zk == 1)
a += b[j];
j++;
}
}
/************集合(follow)相加程序*************/
/************根据字母返回下标程序*************/
int Back(char a) {
for (int i = 0; i < VnNum; i++) {
if (a == Vn[i])
return i;
}
return -1;
}
/************根据字母返回下标程序*************/
/************follow集求解程序*************/
void GetFOLLOW(char a) {
int nk;
nk = Back(a);
int i, j;
i = nk;
if (i == 0) {
//如果待求解字符是开始字符
//则把'#'加入其FOLLOW集
if (IsChar('#', follow[Back(a)]))
;
else
follow[Back(a)] += '#';
}
for (j = 0; j < GenNumNew; j++) {
if (GenerativeNew[j][3] == a && GenerativeNew[j][4] != '\0') {
//如果是A->Bb
if (Is(GenerativeNew[j][4], Vt, VtNum)) {
//如果b是终结符号,直接加入follow(B)
if (IsChar(GenerativeNew[j][4], follow[Back(a)]))
;
else
follow[Back(a)] += GenerativeNew[j][4];
}
else if (Is(GenerativeNew[j][4], Vn, VnNum)) {
//如果b是非终结符号,需要判断
if (IsChar('@', first[Back(GenerativeNew[j][4])])) {
//如果b可以推出空'@',则需要将follow(A)加入follow(B)
GetFOLLOW(GenerativeNew[j][0]);
ADDfollow(follow[Back(a)], follow[Back(GenerativeNew[j][0])]);
}
ADD(follow[Back(a)], first[Back(GenerativeNew[j][4])]);
}
}
else if (GenerativeNew[j][4] == a && GenerativeNew[j][5] != '\0') {
//如果是A->aBb
if (Is(GenerativeNew[j][5], Vt, VtNum)) {
//如果b是终结符号,直接加入follow(B)
if (IsChar(GenerativeNew[j][5], follow[Back(a)]))
;
else
follow[Back(a)] += GenerativeNew[j][5];
}
else if (Is(GenerativeNew[j][5], Vn, VnNum)) {
//如果b是非终结符号,需进行判断
if (IsChar('@', first[Back(GenerativeNew[j][5])])) {
//如果b可以推出空'@',则需要将follow(A)加入follow(B)
GetFOLLOW(GenerativeNew[j][0]);
ADDfollow(follow[Back(a)], follow[Back(GenerativeNew[j][0])]);
}
ADD(follow[Back(a)], first[Back(GenerativeNew[j][5])]);
}
}
else if (GenerativeNew[j][4] == a && GenerativeNew[j][5] == '\0') {
//如果是A->aB
GetFOLLOW(GenerativeNew[j][0]);//直接将follow(A)加入follow(B)
ADDfollow(follow[Back(a)], follow[Back(GenerativeNew[j][0])]);
}
}
}
/************follow集求解程序*************/
/************判断一个字符是否在一个集合中的程序*************/
bool IsChar(char a, string b) {
int i = 0;
while (b[i] != '\0') {
if (a == b[i])
return true;
i++;
}
return false;
}
/************判断一个字符是否在一个集合中的程序*************/
/************预测分析表构建程序*************/
void FAtable() {
int i, j, k;
for (i = 0; i < VtNum; i++) {
for (j = 0; j < GenNumNew; j++) {
if (Vt[i] == GenerativeNew[j][3])
//如果终结符Vt[i]在A->a的first(a)中,则将A->a放入table[A,Vt[i]]中
table[Back(GenerativeNew[j][0])][i] = j;
else if (Is(GenerativeNew[j][3], Vn, VnNum)) {
if (IsChar(Vt[i], first[Back(GenerativeNew[j][3])])) {
table[Back(GenerativeNew[j][0])][i] = j;
}
}
else if (GenerativeNew[j][3] == '@') {
//如果当前的产生式是:A->a且,a='@',则判断当前的Vt[i]是否在
if (IsChar(Vt[i], follow[Back(GenerativeNew[j][0])])) {
table[Back(GenerativeNew[j][0])][i] = j;
}
}
}
}
}
/************预测分析表构建程序*************/
int BBack(char a) {
for (int i = 0; i < VtNum; i++) {
if (a == Vt[i])
return i;
}
return -1;
}
/************文法分析程序*************/
void GAnalysis() {
int i = 0, x, y, k,error=0,n=1;
char abc;
string chan = "";
st.push('#');
st.push(Vn[0]);
abc = st.top();
while (!(abc == word[i] && abc == '#')) {
if (Is(st.top(), Vn, VnNum)) {
x = Back(st.top());
y = BBack(word[i]);
k = table[x][y];//获得产生式
if (k == -1) {
error++;
cout << "步骤["<<n<<"]:识别错误!跳过"<<chan[i]<<";\n";
n++;
i++;
//break;
}
else {
chan = GenerativeNew[k];
k = 0;
st.pop();
while (chan[k] != '\0') {
k++;
}
k--;
if (chan[k] != '@') {
while (chan[k] != '>') {
st.push(chan[k]);
k--;
}
//i++;
cout << "步骤[" << n << "]:用" << chan << "的右部分逆序入栈已经完成;\n";
n++;
}
else {
cout << "步骤[" << n << "]:用" << chan << ";\n";
n++;
//i++;
}
}
}
else if (Is(st.top(), Vt, VtNum)) {
if (st.top() == word[i]) {
cout << "步骤[" << n << "]:匹配栈顶和当前符号" << word[i] << ",成功;\n";
st.pop();
i++;
n++;
}
else {
cout << "步骤[" << n << "]:识别失败!!\n";
n++;
break;
}
}
abc = st.top();
}
if (error) {
cout << "步骤[" << n << "]:识别错误!!错误跳过次数:"<<error<<"\n";
n++;
}
else {
cout << "步骤[" << n << "]:识别成功!!\n";
n++;
}
}
/************文法分析程序*************/
void CHUSHI() {
int i, j;
for (i = 0; i < 100; i++) {
for (j = 0; j < 100; j++) {
table[i][j] = -1;
}
}
}
int main() {
using std::cout;
cout.setf(std::ios::left);
CHUSHI();
GramIn();
GramF();
int i, j;
for (i = 0; i < VnNum; i++) {
GetFIRST(Vn[i]);
}
cc();
for (i = 0; i < VtNum; i++) {
GetFOLLOW(Vn[i]);
}
clearF();
FAtable();
cout << "非终结符的first集为:\n";
for (i = 0; i < VnNum; i++) {
cout << "FIRST("<<Vn[i]<<"):";
cout << first[i] << endl;
}
cout << "非终结符的follow集为:\n";
for (i = 0; i < VnNum; i++) {
cout << "FOLLOW(" << Vn[i] << "):";
cout << follow[i] << endl;
}
cout << "预测分析表为:\n";
cout << "\t";
cout.width(7);
for (i = 0; i < VtNum; i++) {
cout << Vt[i];
cout.width(7);
}
cout << endl;
for (i = 0; i < VnNum; i++) {
cout << Vn[i];
cout.width(7);
for (j = 0; j < VtNum; j++) {
cout << GenerativeNew[table[i][j]];
cout.width(7);
}
cout << endl;
}
WordIn();
cout << "识别结果如下:\n";
GAnalysis();
return 0;
}