算符優先分析法
1.基本概念
-
算符文法(OG):文法G中沒有形如A=>···BC···的產生式,其中B、C為非終結符,則G為算符文法(operator grammar)。
也就是說產生式的右部不能出現兩個非終結符相鄰,就好像算式中兩個操作數相連。
算符文法的兩個性質:
①算符文法中任何句型都不包含兩個相鄰的非終結符。
②如果Ab(bA)出現在算符文法的句型y中,則y中含b的短語必含A,含A的短語不一定含b。
-
算符優先文法(OPG):一個不含ε產生式的算符文法G,任意終結符對(a,b)之間最多只有一種優先關系存在,則G為算符優先文法(operator precedence grammar)。
以算式類比,也就是說我們只關心算符之間的優先關系,不關心操作數的優先關系,·利用算符的優先性和結合性來判斷哪個部分先計算(歸約)。
注意 :這里的優先關系與簡單優先分析法中不一樣。
a、b為終結符,A、B、C為非終結符
-
a和b優先級相等,表示為
a=·b
,當且僅當G中存在產生式規則A=>···ab···或者A=>···aBb···。解讀:表示a、b在同一句柄中同時歸約。
-
a優先級小於b,表示為
a<b
,當且僅當G中存在產生式規則A=>···aB···,且B=+=>b···或B=+=>Cb···。解讀:表示b、a不在一個句柄中,b比a先歸約。
-
a優先級大於b,表示為
a>·b
,當且僅當G中存在產生式規則A=>··Bb···,且B=+=>···a或B=+=>···aC。解讀:表示b、a不在一個句柄中,a比b先歸約。
- FIRSTVT():FIRSTVT(B)={b|B=+=>b···或B=+=>Cb···}
- LASTVT():LASTVT(B)={b|B=+=>···b或B=+=>···bC}
- 素短語:(a)它首先是一個短語,(b)它至少含一個終結符號,(c)除自身外,不再包含其他素短語。
-
2.FIRSTVT()的構造算法
-
原理:
①如果有這樣的表達式:A=>a···或者A=>Ba···,那么a∈FIRSTVT(A)。
②如果有這樣的表達式:B=>A···且有a∈FIRSTVT(A),則a∈FIRSTVT(B)。
-
算法:
數據結構:
布爾數組F[m,n],m為非終結符數量,n為終結符數量,為真時表示對應a∈FIRSTVT(A)。
棧S:暫存用於進行原理②的元素。
流程圖:
FIRSTVT構造算法流程圖
類似原理可以構造LASTVT()的算法。
-
代碼
#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.算符優先關系矩陣的構造算法
-
原理
=·關系
查看所有產生式的右部,尋找A=>···ab···或者A=>···aBb···的產生式,可得a=·b。
<·關系
查看所有產生式的右部,尋找A=>···aB···的產生式,對於每一b∈FIRSTVT(B),可得a<·b。
>·關系
查看所有產生式的右部,尋找A=>··Bb···的產生式,對於每一a∈LASTVT(B),可得a>·b。
-
算法:
流程圖:
算符優先關系矩陣的構造算法流程圖
- 代碼
4.算符優先分析法
讀入字符串為X1X2···Xn#
數組S[n+2]用於存放壓入棧的字符
流程圖:
算符優先分析法流程圖
代碼:
5.實例
算術表達式文法G[E]:
E→E +T | T
T→T * F | F
F→i |(E)
對輸入串i+i#的算符優先分析
-
求非終結符的FIRSTVT()和LASTVT()集:
FIRSTVT()={ + * i ( }
FIRSTVT()={ * i ( }
FIRSTVT()={ i ( }
LASTVT()={ + * i )}
LASTVT()={ * i )}
LASTVT()={ i )}
-
求算符優先關系矩陣
+ | * | ( | ) | i | |
---|---|---|---|---|---|
+ | > | < | < | > | < |
* | > | > | < | > | < |
( | < | < | < | = | < |
) | > | > | > | ||
i | > | > | > |
- 用算符優先分析法進行歸約
S棧 | 優先關系 | 當前符號 | 輸入串 | 動作 |
---|---|---|---|---|
# | < | i | +i# | 移進 |
#i | > | + | i# | 歸約 |
#N | < | + | i# | 移進 |
#N+ | < | i | # | 移進 |
#N+i | > | # | 歸約 | |
#N+N | > | # | 歸約 | |
#N | = | # | 完成 |
語法樹:
語法樹
6. 總結
通過算符優先分析法我們避免了單非終結符的歸約,可以檢查字符串是否為文法的句子,但是可以發現我們無法知道句子是怎么進行歸約的,語法樹也不是真正的語法樹。
同時算符優先分析法也無法完全確定字符串能夠被正確歸約,有一些字符串可能可以通過算符優先分析法進行歸約,但實際上它不是該文法的句子。
算符優先分析法在對文法的要求較高,適用於表達式的語法分析。