(接中篇)
5. 自上而下文法剖析器的實現
經過上面4步精心的準備,最令人激動的時刻到了。一般《編譯原理》課本上的代碼大都是無法在機器上啟動並執行虛擬碼,在這裡,你將要看到的是一個實用的可以檢查錯誤的可以執行求值的基於自上而下文法分析演算法的計算算術運算式的程式。
不失一般性,我們規定算術運算式只可以進行整數的四則運算(含括弧),這樣我們需要擴充下面3個函數:
int E_AddSub(); //對應於非終結符E的產生式
int T_MulDiv(); //對應於非終結符T的產生式
int F_Number(); //對應於非終結符F的產生式
大家看到,上面的函數有傳回值int,我們需要讓這3個函數返回計算出的結果的值。為了計算出每個函數中子運算式的值,在E_AddSub()和T_MulDiv()函數中,我用一個變數rtn來儲存運算子左部的值,用opr2來儲存運算子右部的值,根據運算子進行相應的運算。
為了儲存輸入的算術運算式,我用全域靜態字元數組expr來表示輸入字元緩衝區,用pos來表示字元指標的值,這樣,指標取下一個字元的advance()操作可以用pos++來代替,而指標所指示的字元可以就是expr[pos]。
為了表示錯誤,我用宏定義了6種錯誤的錯誤碼,而且定義了對應的6條錯誤資訊的字串。同時把error()函數改造為:
void Error(int ErrCode);
這樣通過傳遞錯誤碼可以使程式對錯誤進行相應的反應,包括指示錯誤位置、顯示錯誤資訊、發出提示音等。此外,我聲明了出錯跳轉緩衝區靜態變數errjb,errjb是一個std::jmp_buf類型的結構,可以通過setjmp()宏把當前程式的運行狀態記錄到errjb中,錯誤返回時,可以通過longjmp()函數;直接跳轉到main()主程式setjmp()被調用的位置,而不是出錯的函數體中。
這樣,一個功能齊全的算術運算式分析執行器構造完畢,注意,這樣構造的程式不能識別一元運算子,比如輸入“-1+1”會報錯。
下面是運行結果片段:
1+(
^ 語法錯誤 !!! 運算式非法結束或運算式不完整!
請重新輸入!
請輸入一個算術運算式(輸入“Q”或“q”退出):
2-()
^ 語法錯誤 !!! 括弧內無運算式或運算式不完整!
請重新輸入!
請輸入一個算術運算式(輸入“Q”或“q”退出):
2+(3+
^ 語法錯誤 !!! 運算式非法結束或運算式不完整!
請重新輸入!
請輸入一個算術運算式(輸入“Q”或“q”退出):
2+(3*9)+
^ 語法錯誤 !!! 運算式非法結束或運算式不完整!
請重新輸入!
請輸入一個算術運算式(輸入“Q”或“q”退出):
2*(2+4)4
^ 語法錯誤 !!! 右括弧後串連非法字元!
請重新輸入!
程式清單如下:
/****算術運算式的分析和計算,檔案名稱:Exp_c.cpp,代碼/注釋:hifrog****
***** 在VC6和Dev-C下調試通過 ****/
#include
#include
#include
#include
#include
#define EXP_LEN 100 //定義輸入字元緩衝區的長度
/*------------出錯代碼的宏定義--------------*/
#define INVALID_CHAR_TAIL 0 //運算式後跟有非法字元
#define CHAR_AFTER_RIGHT 1 //右括弧後串連非法字元
#define LEFT_AFTER_NUM 2 //數字後非法直接連接左括弧
#define INVALID_CHAR_IN 3 //運算式中含有非法字元
#define NO_RIGHT 4 //缺少右括弧
#define EMPTY_BRACKET 5 //括弧內無運算式
#define UNEXPECTED_END 6 //預期外的算術運算式結束
using namespace std;
const string ErrCodeStr[]= //運算式出錯資訊
{
"運算式後跟有非法字元!",
"右括弧後串連非法字元!",
"數字後非法直接連接左括弧!",
"運算式中含有非法字元!",
"缺少右括弧!",
"括弧內無運算式或運算式不完整!",
"運算式非法結束或運算式不完整!"
};
static char expr[EXP_LEN]; //算術運算式輸入字元緩衝區
static int pos; //字元指標標誌:用來儲存正在分析的字元的位置
static jmp_buf errjb; //出錯跳轉緩衝器
//********以下是函式宣告*********
//產生式“E -> T+E | T-E | T”的函數,用來分析加減算術運算式。
int E_AddSub();
//產生式“T -> F*T | F/T | F”的函數,用來分析乘除算術運算式。
int T_MulDiv();
//產生式“F -> i | (E)”的函數,用來分析數字和括弧內的運算式。
int F_Number();
//出錯處理函數,可以指出錯誤位置,出錯資訊。
void Error(int ErrCode);
int main()
{
int ans; //儲存算術運算式的計算結果
bool quit=false; //是否退出計算
do
{
//在此設定一個跳轉目標,如果本程式的其他函數調用longjmp,
//執行指令就跳轉到這裡,從這裡繼續執行。
if(setjmp(errjb)==0) //如果沒有錯誤
{
pos=0; //初始化字元指標為0,即指向輸入字串的第一個字元。
cout<<"請輸入一個算術運算式(輸入“Q”或“q”退出):"< cin>>expr; //輸入運算式,填充運算式字元緩衝區。
if(expr[0]=='q'||expr[0]=='Q')
//檢查第一個字元,是否退出?
quit=true;
else
{
//調用推導式“E -> T+E | T-E | T”的函數,
//從起始符號“E”開始推導。
ans=E_AddSub();
//此時,程式認為對錶達式的文法分析已經完畢,下面判斷出錯的原因:
//如果運算式中的某個右括弧後直接跟著數字或其他字元,
//則報錯,因為數字i不屬於FOLLOW())集。
if(expr[pos-1]==')'&&expr[pos]!='/0')
Error(CHAR_AFTER_RIGHT);
//如果運算式中的某個數字或右括弧後直接跟著左括弧,
//則報錯,因為左括弧不屬於FOLLOW(E)集。
if(expr[pos]=='(')
Error(LEFT_AFTER_NUM);
//如果結尾有其他非法字元
if(expr[pos]!='/0')
Error(INVALID_CHAR_TAIL);
cout<<"計算得出運算式的值為:"< }
}
else
{
//setjmp(errjb)!=0的情況:
cout<<"請重新輸入!"< }
}
while(!quit);
return 0;
}
//產生式“E -> T+E | T-E | T”的函數,用來分析加減算術運算式。
//返回計算結果
int E_AddSub()
{
int rtn=T_MulDiv(); //計算加減算術運算式的左元
while(expr[pos]=='+'||expr[pos]=='-')
{
int op=expr[pos++]; //取字元緩衝區中當前位置的符號到op
int opr2=T_MulDiv(); //計算加減算術運算式的右元
//計算求值
if(op=='+') //如果是"+"號
rtn+=opr2; //則用加法計算
else //否則(是"-"號)
rtn-=opr2; //用減法計算
}
return rtn;
}
//推導式“T -> F*T | F/T | F”的函數,用來分析乘除算術運算式。
//返回計算結果
int T_MulDiv()
{
int rtn=F_Number(); //計算乘除算術運算式的左元
while(expr[pos]=='*'||expr[pos]=='/')
{
int op=expr[pos++]; //取字元緩衝區中當前位置的符號到op
int opr2=F_Number(); //計算乘除算術運算式的右元
//計算求值
if(op=='*') //如果是"*"號
rtn*=opr2; //則用乘法計算
else //否則(是"/"號)
rtn/=opr2; //用除法計算
}
return rtn;
}
//產生式“F -> i | (E)”的函數,用來分析數字和括弧內的運算式。
int F_Number()
{
int rtn; //聲明儲存傳回值的變數
//用產生式F->(E)推導:
if(expr[pos]=='(') //如果字元緩衝區當前位置的符號是"("
{
pos++; //則指標加一指向下一個符號
rtn=E_AddSub(); //調用產生式“E -> T+E | T-E | T”的分析函數
if(expr[pos++]!=')') //如果沒有與"("匹配的")"
Error(NO_RIGHT); //則產生錯誤
return rtn;
}
if(isdigit(expr[pos])) //如果字元緩衝區中當前位置的字元為數字
{
//則用產生式F -> i推導
//把字元緩衝區中當前位置的字串轉換為整數
rtn=atoi(expr+pos);
//改變指標的值,跳過字元緩衝區的數字部分,找到下一個輸入字元。
while(isdigit(expr[pos]))
pos++;
}
else //如果不是數字則產生相應的錯誤
{
if(expr[pos]==')') //如果發現一個")"
Error(EMPTY_BRACKET); //則是括弧是空的,即括弧內無算術運算式。
else if(expr[pos]=='/0') //如果此時輸入串結束
Error(UNEXPECTED_END); //則算術運算式非法結束
else
Error(INVALID_CHAR_IN); //否則輸入字串中含有非法字元
}
return rtn;
}
//出錯處理函數,輸入錯誤碼,可以指出錯誤位置,出錯資訊。
void Error(int ErrCode)
{
cout<<'/r'; //換行
while(pos--)
cout<<' '; //列印空格,把指示錯誤的"^"移到輸入字串的出錯位置
cout<<"^ 語法錯誤 !!! "
< <
longjmp(errjb,1); //跳轉到main()函數中的setjmp調用處,並設定setjmp(errjb)的傳回值為1
}
(全文完)