再帰降下アナライザーを実装する
実験内容
高級言語の教科書3.2文法の再帰降下分析プログラムを実現します。
要件:
本で提供される入力文字列i1 *(i2 + i3)、または自分で定義した他の記号文字列を使用できます。スタックのすべての内容を出力し、分析結果を提供します。
開発環境
Windows 10
ビジュアルスタジオ2019
データ構造
1シンボルスタック
スタックは、アクセス制限のある線形テーブルであり、片端でのみ挿入または削除操作を許可します。「先入れ先出し」の原則に従います。
再帰降下分析法はトップダウン分析法であり、文法の各非終端記号は再帰プロセス(関数)に対応しており、スタックを使用して分析プロセスを表すことができます。
2分析プロセス
シリアルナンバー | シンボルスタック | 分析する文字 | 表情 |
---|---|---|---|
0 | # | i *(i + i)# | |
1 | #E | i *(i + i)# | E→TE ' |
2 | #E'T | i *(i + i)# | T→FT ' |
3 | #E'T'F | i *(i + i)# | F→i |
4 | #E'T ' | i *(i + i)# | T '→* FT' |
5 | #E'T'F | i *(i + i)# | F→(E) |
6 | #あなたも | i *(i + i)# | E→TE ' |
7 | #E'T'E'T | i *(i + i)# | T→FT ' |
8 | #E'T'E'T'F | i *(i + i)# | F→i |
9 | #E'T'E'T ' | i *(i + i)# | T '→ε |
10 | #あなたも' | i *(i + i)# | E '→+ TE' |
11 | #E'T'E'T | i *(i + i)# | T→FT ' |
12 | #E'T'E'T'F | i *(i + i)# | F→i |
13 | #E'T'E'T ' | i *(i + i)# | T '→ε |
14 | #あなたも' | i *(i + i)# | E '→ε |
15 | #E'T ' | i *(i + i)# | T '→ε |
16 | #E ' | i *(i + i)# | E '→ε |
17 | # |
3アレイ
1.分析文字列配列char token [token_size]:分析する文字列を格納します。
2.シンボルスタックchar stack [10] = {'#'}:シンボルスタックの内容を保存し、最初に '#'を押します。
実験ステップ
分析と設計
教科書のサンプルの質問はすでに分析的に詳細になっ
ています。1.左再帰が排除されました。
2.バックトラックなし。
再帰的降下分析器の構築に直接進みます。
プログラミング
1グローバル変数
①token_len:分析する文字列の長さを格納するint型変数。
②step:現在の分析ステップを記録するint型変数。
2スタック
①char stack [10] = {'#'}:シンボルスタックの内容を格納するcharタイプの配列。最初は '#'を押します。
②stack_top:int型変数。シンボルスタックのトップポインター。
3アレイ
token [token_size]:分析する文字列を格納するchaタイプの配列。
4つの機能
①非終端記号式:
void E();
void E1();
void T();
void T1();
void F();
②印刷関数:void print()。
5疑似コード
「コンパイル原理チュートリアル(第4版)」P52-53をご覧ください
void match(token t){
if (lookahead == t)
lookahead = nexttoken;
else
error();
}
void E(){
T();
E();
}
void E'(){
if (lookahead == '+'){
match('+');
T();
E'();
}
}
void T(){
F();
T'();
}
void T'(){
if (lookahead == '*'){
match('*');
F();
T'();
}
}
void F(){
if (lookahead == 'i')
match('i');
else if (lookahead == '('){
match('(');
E();
if (lookahead == ')')
match(')');
else
error();
}
else
error();
}
実行してデバッグする
デバッグ状況に応じて、対応するコードを変更します。
演算結果
発生した問題と解決策
1 E 'およびT'スタッキング
E 'は文字に属していないため、もともとはストレージを考慮して文字列配列を実装するように計画されていました。多くの問題があるため、E'と '\' 'の2つの文字に従ってE'を直接格納し、T 'は同じです根拠。
2スタックの実装
経験
再帰降下アナライザーの構造をより深く理解し、スタックの実現に熟練している。
実験コード
1つのメイン関数main()
int main(void){
printf("请输入字符串(以#结束):"); //输入待分析字符串
while (token[lookahead] != '#'){
//如果没有按下#结束
char scan_char; //扫描字符
do{
//若没有扫描到结束符#,就继续扫描
scanf_s("%c", &scan_char,1);
token[token_len] = scan_char; //将扫描到的字符保存到待分析串数组中
token_len++;
} while (scan_char != '#');
printf("\n");
printf("序号\t");
printf("符号栈\t\t");
printf("待分析字符\t");
printf("表达式\n");
print(); //打印当前步骤信息
printf("\n"); //换行
stack[++stack_top] = 'E'; //将E()压入栈中,开始分析
step++; //分析步数+1
E();
if (token[lookahead] == '#') //若待分析字符为#,说明符合文法
printf("\n分析成功,合法字符串!");
else
printf("\n分析失败,非法字符串!");
}
return 0;
}
2 E()
/*****************************************************************************
*函数名称:E()
*函数类型:void
*参数:void
*功能:分析文法,输出信息
*****************************************************************************/
void E(){
print(); //打印当前步骤信息
printf("E->TE'\n"); //输出表达式
stack[stack_top] = 'E'; //将产生式右→左压栈
stack[++stack_top] = '\''; //代表E'
stack[++stack_top] = 'T';
step++; //分析步数+1
T();
E1();
}
3 E1()
/*****************************************************************************
*函数名称:E1()
*函数类型:void
*参数:void
*功能:分析文法,输出信息
*****************************************************************************/
void E1(){
if (token[lookahead] == '+'){
//若待分析字符匹配‘+’
print(); //打印当前步骤信息
printf("E'->+TE'\n"); //输出表达式
stack[--stack_top] = 'E'; //将产生式右→左压栈
stack[++stack_top] = '\'';
stack[++stack_top] = 'T';
step++; //分析步数+1
lookahead++; //因为匹配到一个终结符,所以分析下一个字符
T();
E1();
}
else{
print(); //打印当前步骤信息
printf("E'->ε\n"); //输出表达式
stack[stack_top--] = NULL; //出栈
stack[stack_top--] = NULL; //E'虽然为一个非终结符,但占两个字符,T'同
step++; //分析步数+1
}
}
4 T()
/*****************************************************************************
*函数名称:T()
*函数类型:void
*参数:void
*功能:分析文法,输出信息
*****************************************************************************/
void T(){
print(); //打印当前步骤信息
printf("T->FT'\n"); //输出表达式
stack[stack_top] = 'T'; //将产生式右→左压栈
stack[++stack_top] = '\'';
stack[++stack_top] = 'F';
step++; //分析步数+1
F();
T1();
}
5 T1()
/*****************************************************************************
*函数名称:T1()
*函数类型:void
*参数:void
*功能:分析文法,输出信息
*****************************************************************************/
void T1(){
if (token[lookahead] == '*'){
//若待分析字符匹配‘*’
print(); //打印当前步骤信息
printf("T'->*FT'\n"); //输出表达式
stack[--stack_top] = 'T'; //将产生式右→左压栈
stack[++stack_top] = '\'';
stack[++stack_top] = 'F';
step++; //分析步数+1
lookahead++; //因为匹配到一个终结符,所以分析下一个字符
F();
T1();
}
else{
print(); //打印当前步骤信息
printf("T'->ε\n"); //输出表达式
stack[stack_top--] = NULL; //出栈
stack[stack_top--] = NULL;
step++; //分析步数+1
}
}
6 F()
/*****************************************************************************
*函数名称:F()
*函数类型:void
*参数:void
*功能:分析文法,输出信息
*****************************************************************************/
void F(){
if (token[lookahead] == 'i'){
//若待分析字符匹配‘i’
print(); //打印当前步骤信息
printf("F->i\n"); //输出表达式
stack[stack_top--] = NULL; //出栈
step++; //分析步数+1
lookahead++; //因为匹配到一个终结符,所以分析下一个字符
}
else if (token[lookahead] == '('){
//若待分析字符匹配‘(’
print(); //打印当前步骤信息
printf("F->(E)\n"); //输出表达式
stack[stack_top] = 'E'; //将产生式右→左压栈
step++; //分析步数+1
lookahead++; //因为匹配到一个终结符,所以分析下一个字符
E();
if (token[lookahead] == ')'){
//若待分析字符匹配‘)’
lookahead++; //因为匹配到一个终结符,所以分析下一个字符
}
else{
printf("没有')'匹配!\n");
return;
}
}
else{
printf("error\n");
return;
}
}
7 print()
/*****************************************************************************
*函数名称:print()
*函数类型:void
*参数:void
*功能:打印信息
*****************************************************************************/
void print(){
int i;
printf("%d\t", step); //打印分析第step步
for (i = 0; i <= stack_top; i++) //输出分析栈中内容
printf("%c", stack[i]);
if (stack_top < 7) //每列对齐
printf("\t\t");
else
printf("\t");
for (i = 0; i < lookahead; i++){
//若字符已被分析,它的位置置空,保持列对齐
token[i] = ' ';
printf("%c", token[i]);
}
for (i = lookahead; i < token_len; i++) //输出剩余待分析字符
printf("%c", token[i]);
printf("\t"); //为输出表达式做准备
}