首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

PL/0 編譯器前端實作:詞法分析與遞歸下降語法分析器完整設計

     編輯:關於VC++

前言

本文記錄一個 PL/0 語言編譯系統前端的設計與實現,採用「IDE + 編譯器核心」分離架構,透過匿名管道通訊。核心採用循環分支法實現詞法分析、遞歸下降法實現語法分析,輸出符號表與語法錯誤資訊。適合編譯原理教學、輕量級腳本語言開發參考。

PL/0 語言文法定義 (EBNF)

<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 與編譯核心分離

┌──────────────────┐     匿名管道      ┌────────────────────┐
│   IDE (MFC)      │ ◄────────────────► │  PL.exe (編譯核心)  │
│ - 程式碼編輯區   │   stdout/stderr    │ - 詞法分析器        │
│ - 輸出資訊顯示區 │   重定向           │ - 語法分析器        │
│ - 專案管理       │                    │ - 符號表管理        │
└──────────────────┘                    └────────────────────┘
        │                                        │
        │ CreateProcess + CreatePipe             │
        ▼                                        ▼
   執行緒讀取管道                           標準輸出寫入管道

編譯核心類設計:CCompiler

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;   // 語法錯誤
};

關鍵演算法實現細節

1. 詞法分析:循環分支法

  • 掃描輸入字串,逐字符判斷類別:關鍵字、標識符、數字、運算符、界符
  • 使用 JumpNoMatterChar 跳過空格、換行、Tab
  • 識別出的 Token 存入 m_vectorSymbol (vector 實現動態符號表)
  • 支援保留字:program, const, var, procedure, begin, end, if, then, else, while, do, call, read, write, odd

2. 語法分析:遞歸下降法

  • 每個非終結符對應一個成員函數(如 SyntaxAnalysis_Exp 對應 <exp>)
  • 函數間透過調用關係直接映射文法產生式
  • 使用 m_iCurPointer 追蹤符號表當前位置,實現「前瞻一符號」
  • 錯誤處理:遇到不匹配時記錄 SYNTAXERR 至 m_vectorSyntaxErr,嘗試同步恢復(跳過至下一個 ; 或 end)

3. IDE 與核心進程通訊

// 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),管道通訊雖簡單但有效驗證了進程間協作模型。後續可擴展語義分析、中間碼生成、目標碼優化等後端模組。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved