編譯原理系列之五 自底向上優先分析(2)-算符優先分析法


算符優先分析法

1.基本概念

  1. 算符文法(OG):文法G中沒有形如A=>···BC···的產生式,其中B、C為非終結符,則G為算符文法(operator grammar)。

    也就是說產生式的右部不能出現兩個非終結符相鄰,就好像算式中兩個操作數相連。

    算符文法的兩個性質:

    ①算符文法中任何句型都不包含兩個相鄰的非終結符。

    ②如果Ab(bA)出現在算符文法的句型y中,則y中含b的短語必含A,含A的短語不一定含b。

  2. 算符優先文法(OPG):一個不含ε產生式的算符文法G,任意終結符對(a,b)之間最多只有一種優先關系存在,則G為算符優先文法(operator precedence grammar)。

    以算式類比,也就是說我們只關心算符之間的優先關系,不關心操作數的優先關系,·利用算符的優先性和結合性來判斷哪個部分先計算(歸約)。

    注意 :這里的優先關系與簡單優先分析法中不一樣。

    a、b為終結符,A、B、C為非終結符

    1. a和b優先級相等,表示為 a=·b ,當且僅當G中存在產生式規則A=>···ab···或者A=>···aBb···。

      解讀:表示a、b在同一句柄中同時歸約。

    2. a優先級小於b,表示為 a<b ,當且僅當G中存在產生式規則A=>···aB···,且B=+=>b···或B=+=>Cb···。

      解讀:表示b、a不在一個句柄中,b比a先歸約。

    3. a優先級大於b,表示為 a>·b ,當且僅當G中存在產生式規則A=>··Bb···,且B=+=>···a或B=+=>···aC。

      解讀:表示b、a不在一個句柄中,a比b先歸約。

    1. FIRSTVT():FIRSTVT(B)={b|B=+=>b···或B=+=>Cb···}
    2. LASTVT():LASTVT(B)={b|B=+=>···b或B=+=>···bC}
    3. 素短語:(a)它首先是一個短語,(b)它至少含一個終結符號,(c)除自身外,不再包含其他素短語。

2.FIRSTVT()的構造算法

  1. 原理:

    ①如果有這樣的表達式:A=>a···或者A=>Ba···,那么a∈FIRSTVT(A)。

    ②如果有這樣的表達式:B=>A···且有a∈FIRSTVT(A),則a∈FIRSTVT(B)。

  2. 算法:

    數據結構:

    ​ 布爾數組F[m,n],m為非終結符數量,n為終結符數量,為真時表示對應a∈FIRSTVT(A)。

    ​ 棧S:暫存用於進行原理②的元素。

    流程圖:

2869373-449e2c15827800d3.png

FIRSTVT構造算法流程圖

類似原理可以構造LASTVT()的算法。

  1. 代碼

    #include <iostream>
    #include "fstream"
    #include <stack>
    using namespace std;
    
    /**
    * 文法輸入文件格式要求:
    * 所有符號均為單個字符,
    * 每個產生式占一行,
    * 非終結符用大寫字母A-Z表示,
    * 終結符用小寫字母a-z和其他符號表示
    * 產生式右部用|表示或
    * 
    * 示例:
    * S->#E#
    * E->E+T
    * E->T
    * T->T*F
    * T->F
    * F->P!F|P
    * P->(E)
    * P->i
    */
    
    #define MAX_NUM 30 //非終結符最大數量
    
    //產生式的前兩個字符
    class EasyProductions
    {
    public:
     char firstChar;
     char secondChar;
     EasyProductions *nextPtr = NULL;
    };
    
    //Tchar∈firstvt(Vchar)
    class VTRelation
    {
    public:
     char Tchar;
     char Vchar;
    };
    
    //文法中終結符,非終結符數目
    int tNum, vNum;
    //儲存終結符,非終結符
    char tSymb[MAX_NUM], vSymb[MAX_NUM];
    //存放VTRelation的棧
    stack<VTRelation *> stk;
    //26個非終結符的EasyProductions鏈表指針,序號為對應ascii碼
    EasyProductions *productions[MAX_NUM];
    //firstvt矩陣
    int **firstvt;
    
    //判斷是否為非終結符
    bool isTerminal(char c)
    {
     if (c <= 90 && c >= 65)
         return false;
     else
         return true;
    }
    
    //判斷終結符是否已經保存至數組
    bool isInTNum(char c)
    {
     for (int i = 0; i < tNum; i++)
     {
         if (tSymb[i] == c)
             return true;
     }
     return false;
    }
    
    //判斷終結符是否已經保存至數組
    bool isInVNum(char c)
    {
     for (int i = 0; i < vNum; i++)
     {
         if (vSymb[i] == c)
             return true;
     }
     return false;
    }
    //通過終結符字符找在數組中的編號
    int findIndexByTchar(char c)
    {
     for (int i = 0; i < tNum; i++)
     {
         if (tSymb[i] == c)
         {
             return i;
         }
     }
     return -1;
    }
    //通過非終結符字符找在數組中的編號
    int findIndexByVchar(char c)
    {
     for (int i = 0; i < vNum; i++)
     {
         if (vSymb[i] == c)
         {
             return i;
         }
     }
     return -1;
    }
    //將出現過的終結符或非終結符保存至數組
    void saveSymb(char c)
    {
     if (isTerminal(c))
     { //是終結符
         if (!isInTNum(c))
         {
             tSymb[tNum] = c;
             tNum++;
         }
     }
     else
     { //是非終結符
         if (!isInVNum(c))
         {
             vSymb[vNum] = c;
             vNum++;
         }
     }
    }
    
    //解析每條產生式的前兩個字符
    void analysisGrammar(char buffer[], EasyProductions *productions[])
    {
     int start = 3;  //從3開始為產生式右部開頭
     int offset = 0; //距離產生式右部開頭的偏移量
     saveSymb(buffer[0]);
     while (buffer[start + offset] != 0) //產生式讀完 //沒有換行符
     {
         if (buffer[start + offset] != '|')
             saveSymb(buffer[start + offset]);
         if (offset == 0 && buffer[start + offset] != '|') //開頭
         {
             if (buffer[start + offset + 1] != 0 || buffer[start + offset + 1] != '|') //開頭有兩字符
             {
                 EasyProductions *production = new EasyProductions;
                 production->firstChar = buffer[start + offset];
                 production->secondChar = buffer[start + offset + 1];
                 if (productions[buffer[0] - 65] == NULL)
                 {
                     productions[buffer[0] - 65] = production;
                 }
                 else //頭插法
                 {
                     EasyProductions *temp = productions[buffer[0] - 65]->nextPtr;
                     productions[buffer[0] - 65]->nextPtr = production;
                     production->nextPtr = temp;
                 }
             }
             else //開頭只有一個字符
             {
                 EasyProductions *production = new EasyProductions;
                 production->firstChar = buffer[start + offset];
                 production->secondChar = 0;
                 if (productions[buffer[0] - 65] == NULL)
                 {
                     productions[buffer[0] - 65] = production;
                 }
                 else //頭插法
                 {
                     EasyProductions *temp = productions[buffer[0] - 65]->nextPtr;
                     productions[buffer[0] - 65]->nextPtr = production;
                     production->nextPtr = temp;
                 }
             }
             offset++;
         }
         else //不是開頭,跳過
         {
             if (buffer[start + offset] == '|')//重新開頭
             {
                 offset++;
                 start = start + offset;
                 offset = 0;
             }
             else
             {
                 offset++;
             }
         }
     }
    }
    
    //讀取當前目錄下grammar.txt並解析文法
    int readGrammar(EasyProductions *productions[])
    {
     char buffer[256];
     ifstream inf("grammar.txt");
     if (!inf.is_open())
     {
         cout << "Error opening file" << endl;
         return -1;
     }
     while (!inf.eof()) //每讀一句產生式就解析
     {
         inf.getline(buffer, 100);
         analysisGrammar(buffer, productions);
     }
     return 0;
    }
    
    //算法原理第一步:如果有這樣的表達式:A=>a···或者A=>Ba···,那么a∈FIRSTVT(A)。
    void stepOne()
    {
     for (int i = 0; i < MAX_NUM; i++) //遍歷每個非終結符的EasyProductions
     {
         EasyProductions *production;
         production = productions[i]; //鏈表頭指針
         while (production != NULL)
         {
             if (isTerminal(production->firstChar))
             { //產生式第一個字符是終結符
                 //保存至矩陣中
                 firstvt[findIndexByVchar((char)(i + 65))][findIndexByTchar(production->firstChar)] = 1;
                 //保存到關系中並壓棧
                 VTRelation *relation = new VTRelation;
                 relation->Vchar = (char)(i + 65);
                 relation->Tchar = production->firstChar;
                 stk.push(relation);
             }
             else if (isTerminal(production->secondChar) && production->secondChar != 0)
             { //產生式第一個字符不是終結符但第二個字符是終結符
                 //保存至矩陣中
                 firstvt[findIndexByVchar((char)(i + 65))][findIndexByTchar(production->secondChar)] = 1;
                 //保存到關系中並壓棧
                 VTRelation *relation = new VTRelation;
                 relation->Vchar = (char)(i + 65);
                 relation->Tchar = production->secondChar;
                 stk.push(relation);
             }
             production = production->nextPtr; //指向下個節點
         }
     }
    }
    //算法原理第二步:B=>A···且有a∈FIRSTVT(A),則a∈FIRSTVT(B)
    void stepTwo()
    {
     while (!stk.empty())
     { //棧不為空
         VTRelation *relation = stk.top();
         int nnn = stk.size();
         stk.pop();
         nnn = stk.size();                 //彈出,得到(A,a)
         for (int i = 0; i < MAX_NUM; i++) //遍歷每個非終結符的EasyProductions
         {
             if ((char)i == relation->Vchar) //A->···時跳過
                 continue;
             EasyProductions *production;
             production = productions[i]; //鏈表頭指針
             while (production != NULL)
             {
                 if (production->firstChar == relation->Vchar) //找到B->A···,得到(B,a)
                 {
                     if (firstvt[findIndexByVchar((char)(i + 65))][findIndexByTchar(relation->Tchar)] == 1) //已知則跳過
                     {
                         production = production->nextPtr; //指向下個節點
                         continue;
                     }
                     //保存至矩陣中
                     firstvt[findIndexByVchar((char)(i + 65))][findIndexByTchar(relation->Tchar)] = 1;
                     //保存到關系中並壓棧
                     VTRelation *relationStack = new VTRelation;
                     relationStack->Vchar = (char)(i + 65);
                     relationStack->Tchar = relation->Tchar;
                     stk.push(relationStack);
                 }
                 production = production->nextPtr; //指向下個節點
             }
         }
     }
    }
    //打印FirstVT矩陣
    void printFirstVT()
    {
     for (int i = 0; i < vNum; i++)
     {
         cout << "FirstVT(" << vSymb[i] << ")={ ";
         for (int j = 0; j < tNum; j++)
         {
             if (firstvt[i][j] == 1)
             {
                 cout << tSymb[j] << " ";
             }
         }
         cout << "}" << endl;
     }
    }
    
    /**
    * FirstVT構造算法
    */
    int main()
    {
    
     //初始化terminal數組
     for (int i = 0; i < MAX_NUM; i++)
         productions[i] = NULL;
     tNum = 0, vNum = 0;
     readGrammar(productions);
     //初始化firstvt矩陣
     firstvt = new int *[vNum]; //非終結符為行
     for (int i = 0; i < vNum; i++)
         firstvt[i] = new int[tNum]; //終結符為列
     for (int i = 0; i < vNum; i++)
         for (int j = 0; j < tNum; j++)
             firstvt[i][j] = 0;
     stepOne();
     stepTwo();
     printFirstVT();
     return 0;
    }
    
    

3.算符優先關系矩陣的構造算法

  1. 原理

    =·關系

    查看所有產生式的右部,尋找A=>···ab···或者A=>···aBb···的產生式,可得a=·b。

    <·關系

    查看所有產生式的右部,尋找A=>···aB···的產生式,對於每一b∈FIRSTVT(B),可得a<·b。

    >·關系

    查看所有產生式的右部,尋找A=>··Bb···的產生式,對於每一a∈LASTVT(B),可得a>·b。

  2. 算法:

    流程圖:

2869373-01af4d96555dbd6e.png

算符優先關系矩陣的構造算法流程圖

  1. 代碼

4.算符優先分析法

讀入字符串為X1X2···Xn#

數組S[n+2]用於存放壓入棧的字符

流程圖:

2869373-ba6c7530ad647ccf.png

算符優先分析法流程圖

代碼:

5.實例

算術表達式文法G[E]:
E→E +T | T
T→T * F | F
F→i |(E)
對輸入串i+i#的算符優先分析

  1. 求非終結符的FIRSTVT()和LASTVT()集:

    FIRSTVT()={ + * i ( }

    FIRSTVT()={ * i ( }

    FIRSTVT()={ i ( }

    LASTVT()={ + * i )}

    LASTVT()={ * i )}

    LASTVT()={ i )}

  2. 求算符優先關系矩陣

  + * i
+ > < < > <
* > > < > <
( < < < = <
) > >   >  
i > >   >  
  1. 用算符優先分析法進行歸約
S棧 優先關系 當前符號 輸入串 動作
# < i +i# 移進
#i > + i# 歸約
#N < + i# 移進
#N+ < i # 移進
#N+i > #   歸約
#N+N > #   歸約
#N = #   完成

語法樹:

2869373-76cdbff6f3f76589.png

語法樹

6. 總結

通過算符優先分析法我們避免了單非終結符的歸約,可以檢查字符串是否為文法的句子,但是可以發現我們無法知道句子是怎么進行歸約的,語法樹也不是真正的語法樹。

同時算符優先分析法也無法完全確定字符串能夠被正確歸約,有一些字符串可能可以通過算符優先分析法進行歸約,但實際上它不是該文法的句子。

算符優先分析法在對文法的要求較高,適用於表達式的語法分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM