摘要:本文介紹了一個PL/0語言的詞法及語法分析系統的設計與實現
關鍵詞:循環分支 遞歸下降 管道 輸出重定向
現在的編譯系統都是IDE(Integrated Development Environment)和編譯器獨立實現,他們之間通過管道通信,本系統也采用這一方法來實現。我首先給出本文中的PL/0語言的文法:
PL/0語言的BNF描述(擴充的巴克斯范式表示法)<prog> → program <id>;<block>
<block> → [<condecl>][<vardecl>][<proc>]<body>
<condecl> → const <const>{,<const>}
<const> → <id>:=<integer>
<vardecl> → var <id>{,<id>}
<proc> → procedure <id>(<id>{,<id>});<block>{;<proc>}
<body> → begin <statement>{;<statement>}end
<statement> → <id> := <exp>
|if <lexp> then <statement>[else <statement>]
|while <lexp> do <statement>
|call <id>[(<exp>{,<exp>})]
|<body>
|read (<id>{,<id>})
|write (<exp>{,<exp>})
<lexp> → <exp> <lop> <exp>|odd <exp>
<exp> → [+|-]<term>{<aop><term>}
<term> → <factor>{<mop><factor>}
<factor>→<id>|<integer>|(<exp>)
<lop> → =|<>|<|<=|>|>=
<aop> → +|-
<mop> → *|/
<id> → l{l|d} (注:l表示字母)
<integer> → d{d}
注釋:
<prog>:程序 ;<block>:塊、程序體 ;<condecl>:常量說明 ;<const>:常量;
<vardecl>:變量說明 ;<proc>:分程序 ; <body>:復合語句 ;<statement>:語句;
<exp>:表達式 ;<lexp>:條件 ;<term>:項 ; <factor>:因子 ;<aop>:加法運算符;
<mop>:乘法運算符; <lop>:關系運算符
odd:判斷表達式的奇偶性。
下面我們先來看看詞法及語法分析器的設計與實現。詞法分析采用循環分支方法實現,語法分析采用遞歸下降來實現。它們的程序流程圖如下:
下面我們來實現這個兩個分析器。這兩個分析器采用一個類CCompiler來實現,這個類的定義如下:
//編譯類class CCompiler
{
public:
CCompiler();
virtual ~CCompiler();
public:
void Compile(char *szFile); //編譯,公共接口
vector<SYNTAXERR> GetSyntaxErr(){return m_vectorSyntaxErr;}; //得到語法錯誤
protected:
bool LexAnalysis(char *szStr); //詞法分析
bool IsOprSym(char *szStr); //是否為運算符
bool IsBndSym(char *szStr); //是否為界符
bool IsKeyWord(char *szStr); //是否為關鍵字
bool IsInSymbolTab(char *szStr); //是否已在符號表中
char* JumpNoMatterChar(char *szStr); //跳過空格,回車,換行符,Tab
void OutSymbolTab(char *szFile); //輸出符號表到文件
void SyntaxAnalysis(); //語法分析
void SyntaxAnalysis_Prog();
bool SyntaxAnalysis_Mop();
bool SyntaxAnalysis_Integer();
bool SyntaxAnalysis_Aop();
bool SyntaxAnalysis_Lop();
int SyntaxAnalysis_Id();
int SyntaxAnalysis_Block();
int SyntaxAnalysis_Body();
int SyntaxAnalysis_Factor();
int SyntaxAnalysis_Term();
int SyntaxAnalysis_Lexp();
int SyntaxAnalysis_Exp();
int SyntaxAnalysis_Statement();
int SyntaxAnalysis_Const();
int SyntaxAnalysis_Proc();
int SyntaxAnalysis_Vardecl();
int SyntaxAnalysis_Condecl();
protected:
int m_iVecotrSymbolSize; //符號表大小
int m_iCurPointer; //符號表中當前指針
vector<LEXPROPERTYVS> m_vectorSymbol; //符號表
vector<SYNTAXERR> m_vectorSyntaxErr; //語法錯誤代碼
};
其中:函數bool LexAnalysis(char *szStr);是對輸入字符串szStr采用循環分支方法進行詞法分析,分析出來的符號放在符號表m_vectorSymbol中,這個符號表采用向量這個數據結構來表示。詞法分析得出符號表後,即進入語法分析階段,語法分析由函數void SyntaxAnalysis();完成。下面這些函數是各非終結符對應的遞歸子程序。bool SyntaxAnalysis_Mop();
bool SyntaxAnalysis_Integer();
bool SyntaxAnalysis_Aop();
bool SyntaxAnalysis_Lop();
int SyntaxAnalysis_Id();
int SyntaxAnalysis_Block();
int SyntaxAnalysis_Body();
int SyntaxAnalysis_Factor();
int SyntaxAnalysis_Term();
int SyntaxAnalysis_Lexp();
int SyntaxAnalysis_Exp();
int SyntaxAnalysis_Statement();
int SyntaxAnalysis_Const();
int SyntaxAnalysis_Proc();
int SyntaxAnalysis_Vardecl();
int SyntaxAnalysis_Condecl();
以上我介紹了詞法及語法分析核心的設計實現,下面我簡單介紹下IDE的實現和IDE與分析核心之間的通信。本系統的IDE與分析核心之間采用管道通信,代碼如下:
DWORD dwThreadID;
::CreateThread(0,0,CompileThread,this,0,&dwThreadID); //創建進程
進程創建後調用進程函數,
//進程函數
DWORD WINAPI CompileThread(LPVOID pParam)
{
CCompileSysView *pView=(CCompileSysView*)pParam;
pView->GetCompileResult();
return 0;
}
進程函數調用類的自身函數GetCompileResult();得到分析核心的輸出結果,這個函數的實現如下:void CCompileSysView::GetCompileResult()
{
SECURITY_ATTRIBUTES sa;
HANDLE hRead,hWrite;
CString strFile;
CString strOut;
strFile.Format("..\\pl\\pl.exe "); //指定分析核心程序的路徑
//當前文件作為參數傳給分析核心程序,防止這個文件名中含有空格,故用雙引號""將文件名括住
strFile=strFile+(char)34+m_szCurFile+(char)34;
sa。nLength=sizeof(SECURITY_ATTRIBUTES);
sa。lpSecurityDescriptor=NULL;
sa。bInheritHandle=TRUE;
if(!CreatePipe(&hRead,&hWrite,&sa,0))//創建管道進行通信
{
MessageBox("Error On CreatePipe()");
return;
}
STARTUPINFO si;
PROCESS_INFORMATION pi;
si。cb=sizeof(STARTUPINFO);
GetStartupInfo(&si);
si。hStdError=hWrite;
si。hStdOutput=hWrite; //輸出重定向到文件
si。wShowWindow=SW_HIDE;
si。dwFlags=STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
//創建進程啟動分析核心程序
if(!CreateProcess(NULL,(LPSTR)(LPCTSTR)strFile,NULL,NULL,TRUE,NULL,NULL,NULL,&si,&pi))
{
MessageBox("Error on CreateProcess()");
return;
}
CloseHandle(hWrite);
char buffer[4096]={0};
DWORD bytesRead;
while(true)
{
if(!ReadFile(hRead,buffer,4095,&bytesRead,NULL))
break;
strOut+=buffer;
m_pwndOutBar->SetColorRichEditText(strOut); //將輸出結果顯示出來
Sleep(500);
}
}
這樣就完成了整個分析系統的設計與實現。下面我們來看看整個系統是怎樣運行的。我們先來看看這個系統的運行界面:
程序運行後,出現如圖所示的界面,首先設置分析程序的路徑,方法是:點菜單IDE環境(I),設置,會出現如下圖所示的對話框:
在編輯框中輸入分析器所在路徑即可(默認分析器和源文件在一個目錄下)。設置好以後,就可以在代碼編輯區輸入代碼了,或者點“打開”打開文件,然後點擊工具欄“啟動”(也可按快捷鍵F7)按鈕進行分析,分析完以後,詞法分析結果會在“分析結果顯示區”顯示,詞法和語法分析信息會在"輸出信息顯示區"顯示。
已知的 bug 說明
由於時間關系,現有如下Bug本人未能調試出來,若有高手調試出來的話,還望告知。
PL.exe 有大量內存洩漏,但是本人在 CCompiler 的析構函數中用如下代碼釋放內存,不知為何出錯:CCompiler::~CCompiler()
{
//下面這段釋放內存的代碼不知道為什麼出錯
// for(int i=0;i<m_iVecotrSymbolSize;i++)
// delete m_vectorSymbol[i].szStr;
}
用測試源文件中的 Test3.pas 測試 PL.exe 時,不知道為什麼在Debug狀態下不出錯,而在Release狀態下出錯。
用測試源文件中的Test3.pas測試 IDE.exe 時,輸出信息欄會多出一些前面已經顯示過的信息,不知道為什麼,估計讀管道信息時,又把原來的已經讀過了的信息又讀了一遍。
源碼編輯區的行和列顯示問題:我目前只顯示了行,列還不能顯示。
工作區間欄:點擊右鍵,再選“展開”。有時候會出現不了你想要的效果,再用右鍵點擊時,必須先用左鍵點擊,這樣才能得到你想要的效果,原因是:函數GetSelectedItem()得到的選中的項必須先用左鍵點擊 ,不知道怎樣才能解決這個問題。
雙擊工作空間的最子節點時,應該使該節點對應的單詞進入用戶的視區范圍內。
類CIDEView中的函數GetCompileResult()中的一段代碼,在Release版本中運行沒有出錯,在Debug版本中出錯.代碼如下 :pDoc->SetPathName(strFile,1);
pDoc->SetModifiedFlag(0);
pDoc->OnSaveDocument((LPSTR)(LPCSTR)strFile); //先保存該文件
str=pDoc->GetTitle();
pDoc->SetTitle(str);
if(str.Right(1)=="*")
{
str=str.Left(str.GetLength()-1);
pDoc->SetTitle(str);
}
UpdateWindow();
這段代碼的意思就是在啟動分析程序之前先保存文件並把窗口上做未保存標記的星號去掉。
本文配套源碼