本文記錄一個 PL/0 語言編譯系統前端的設計與實現,採用「IDE + 編譯器核心」分離架構,透過匿名管道通訊。核心採用循環分支法實現詞法分析、遞歸下降法實現語法分析,輸出符號表與語法錯誤資訊。適合編譯原理教學、輕量級腳本語言開發參考。
<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> → letter { letter | digit }
<integer> → digit { digit }
┌──────────────────┐ 匿名管道 ┌────────────────────┐
│ IDE (MFC) │ ◄────────────────► │ PL.exe (編譯核心) │
│ - 程式碼編輯區 │ stdout/stderr │ - 詞法分析器 │
│ - 輸出資訊顯示區 │ 重定向 │ - 語法分析器 │
│ - 專案管理 │ │ - 符號表管理 │
└──────────────────┘ └────────────────────┘
│ │
│ CreateProcess + CreatePipe │
▼ ▼
執行緒讀取管道 標準輸出寫入管道
class CCompiler {
public:
CCompiler(); virtual ~CCompiler();
void Compile(char *szFile); // 公共介面:編譯指定檔案
vector<SYNTAXERR> GetSyntaxErr(); // 取得語法錯誤列表
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); // 跳過空白字符
void OutSymbolTab(char *szFile); // 匯出符號表到檔案
// 語法分析:遞歸下降法
void SyntaxAnalysis(); // 入口
void SyntaxAnalysis_Prog();
int SyntaxAnalysis_Block();
int SyntaxAnalysis_Body();
int SyntaxAnalysis_Statement();
int SyntaxAnalysis_Const();
int SyntaxAnalysis_Vardecl();
int SyntaxAnalysis_Condecl();
int SyntaxAnalysis_Proc();
int SyntaxAnalysis_Exp();
int SyntaxAnalysis_Term();
int SyntaxAnalysis_Factor();
int SyntaxAnalysis_Lexp();
int SyntaxAnalysis_Id();
bool SyntaxAnalysis_Integer();
bool SyntaxAnalysis_Aop();
bool SyntaxAnalysis_Mop();
bool SyntaxAnalysis_Lop();
protected:
int m_iVectorSymbolSize;
int m_iCurPointer;
vector<LEXPROPERTYVS> m_vectorSymbol; // 符號表
vector<SYNTAXERR> m_vectorSyntaxErr; // 語法錯誤
};
JumpNoMatterChar 跳過空格、換行、Tabm_vectorSymbol (vector 實現動態符號表)program, const, var, procedure, begin, end, if, then, else, while, do, call, read, write, oddSyntaxAnalysis_Exp 對應 <exp>)m_iCurPointer 追蹤符號表當前位置,實現「前瞻一符號」SYNTAXERR 至 m_vectorSyntaxErr,嘗試同步恢復(跳過至下一個 ; 或 end)// IDE 端:建立管道並啟動 PL.exe
SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
HANDLE hRead, hWrite;
CreatePipe(&hRead, &hWrite, &sa, 0);
STARTUPINFO si = {sizeof(STARTUPINFO)};
GetStartupInfo(&si);
si.hStdOutput = hWrite;
si.hStdError = hWrite;
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
CString cmd = "..\\pl\\pl.exe \"" + m_szCurFile + "\"";
CreateProcess(NULL, cmd.GetBuffer(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
CloseHandle(hWrite);
// 背景執行緒持續讀取管道輸出
char buffer[4096]; DWORD bytesRead;
while (ReadFile(hRead, buffer, 4095, &bytesRead, NULL)) {
strOut += buffer;
m_pwndOutBar->SetColorRichEditText(strOut); // 即時顯示
Sleep(500);
}
| 問題 | 現象 | 可能原因 / 改進方向 |
|---|---|---|
| 記憶體洩漏 | PL.exe 大量洩漏 | 析構函數中 delete m_vectorSymbol[i].szStr 導致錯誤 → 需檢查擁有權語意,改用 std::string 或智慧指標 |
| Release 模式崩潰 | Debug 正常,Release 崩潰 | 未初始化變數、邊界越界、優化導致時序差異 → 開啟 /RTCs /GS 檢查 |
| 管道重複讀取 | IDE 輸出欄顯示重複舊資訊 | ReadFile 未正確處理 EOF/緩衝區殘留 → 需在讀取前清空緩衝區或檢查 bytesRead==0 |
| 編輯器行/列顯示不全 | 僅顯示行號 | 需解析 RichEdit 控件 EM_LINEFROMCHAR/EM_LINEINDEX 計算列號 |
| 工作區樹狀右鍵菜單異常 | 需先左鍵點選才能右鍵展開 | GetSelectedItem() 需焦點項 → 改用 HitTest 取得滑鼠下項目 |
此 PL/0 編譯系統展示了編譯器前端經典實現路徑:正規文法 → 詞法分析器 (DFA/循環分支) → 語法分析器 (LL(1)/遞歸下降) → 符號表/錯誤處理。IDE 與核心分離架構符合現代編譯器設計(如 clangd/LSP),管道通訊雖簡單但有效驗證了進程間協作模型。後續可擴展語義分析、中間碼生成、目標碼優化等後端模組。