編譯原理實驗二:LL(1)語法分析器


一、實驗要求

  

  1. 提取左公因子或消除左遞歸(實現了消除左遞歸)

  2. 遞歸求First集和Follow集

  其它的只要按照課本上的步驟順序寫下來就好(但是代碼量超多...),下面我貼出實驗的一些關鍵代碼和算法思想。

二、基於預測分析表法的語法分析

  2.1 代碼結構

    2.1.1  Grammar類

       功能:主要用來處理輸入的文法,包括將文法中的終結符和非終結符分別存儲,檢測直接左遞歸和左公因子,消除直接左遞歸,獲得所有非終結符的First集,Follow集以及產生式的Select集。

#ifndef GRAMMAR_H
#define GRAMMAR_H
#include <string>
#include <cstring>
#include <iostream>
#include <vector>
#include <set>
#include <iomanip>
#include <algorithm>
using namespace std;
const int maxn = 110;

//產生式結構體 
struct EXP{
    char left;      //左部 string right;   //右部
};

class Grammar
{
    public:
        Grammar();               //構造函數
        bool isNotTer(char x);   //判斷是否是終結符
        int  getTer(char x);     //獲取終結符下標 
        int  getNonTer(char x);  //獲取非終結符下標
        void getFirst(char x);   //獲取某個非終結符的First集
        void getFollow(char x);  //獲取某個非終結符的Follow集
        void getSelect(char x);  //獲取產生式的Select集
        void input();            //輸入文法
        void scanExp();          //掃描輸入的產生式,檢測是否有左遞歸和左公因子 
        void remove();           //消除左遞歸 
        void solve();            //處理文法,獲得所有First集,Follow集以及Select集
        void display();          //打印First集,Follow集,Select集
        void debug();            //用於debug的函數 
        ~Grammar();               //析構函數 
    protected:
        int cnt;                 //產生式數目
        EXP exp[maxn];           //產生式集合
        set<char> First[maxn];   //First集
        set<char> Follow[maxn];  //Follow集
        set<char> Select[maxn];  //select集
        vector<char> ter_copy;   //去掉$的終結符
        vector<char> ter;        //終結符
        vector<char> not_ter;    //非終結符
};
#endif

       2.1.2  AnalyzTable類

    功能:得到預測分析表,判斷輸入的文法是否是LL(1)文法,用預測分析表法判斷輸入的符號串是否符合剛才輸入的文法,並打印出分析過程。

#ifndef ANALYZTABLE_H
#define ANALYZTABLE_H
#include "Gramma.h"
class AnalyzTable:public Gramma
{
    public:
        AnalyzTable();
        void getTable();          //得到分析表
        void judge();             //判斷是否是LL(1)文法 
        void analyExp(string s);  //分析輸入串 
        void displayTable();      //打印表
        void inputString();       //輸入符號串 
        ~AnalyzTable();
    protected:
        string s;                 //符號串
        vector<char> stack;       //分析棧
        vector<char> left;        //剩余輸入串
        int detect[maxn][maxn];   //檢測表
        int table[maxn][maxn];    //預測分析表
};
#endif

  2.2 記號和規定

    • 非終結符:大寫字母'A'~'Z'

    • 終結符:除大寫字母之外的所有字符

    • 空串:$

    • 符號棧終止符:#

    • 規定第一個產生式的左邊那個非終結符就是開始符號

    • 輸入的產生式需要分開寫(比如A->a|b, 要輸入A->a和A->b才能處理)

  2.3 算法思想

    2.3.1 求First集的算法思想      

  •  遍歷每一個左部為x的產生式

  •  如果產生式右部第一個字符為終結符,則將其計入左部非終結符的First集

  •  如果產生式右部第一個字符為非終結符

  •  求該非終結符的First集

  •  將該非終結符的去掉$的First集計入左部的First集

  •  若存在$,繼續往后遍歷右部

  •  若不存在$,則停止遍歷該產生式,進入下一個產生式

  •  若已經到達產生式的最右部的非終結符(即右部的First集都含有空串),則將$加入左部的First集

  •  處理數組中重復的First集中的終結符

 

//求出非終結符的First集 
void Gramma::getFirst(char x){
    cout<<x<<endl;
    bool flag = 0;  //記錄非終結符的First集是否有空串 
    int tot = 0;    //記錄一個非終結符產生式含有空串的產生式
    for(int i=0;i<cnt;i++){
        if(exp[i].left==x){
            //如果右部的第一個字符是終結符 
            if(!isNotTer(exp[i].right[0])){
                First[getNonTer(x)].insert(exp[i].right[0]);
            }
            //如果是非終結符
            else{
                //從左到右遍歷右部 
                for(int j=0;j<exp[i].right.length();j++){
                    //如果遇到終結符,結束
                    if(!isNotTer(exp[i].right[j])){
                        First[getNonTer(x)].insert(exp[i].right[j]);
                        break;
                    }
                    //不是終結符,求該非終結符的First集
                    getFirst(exp[i].right[j]);
                    set<char>::iterator it;
                    int ind = getNonTer(exp[i].right[j]); 
                    for(it=First[ind].begin();it!=First[ind].end();it++){
                        if(*it=='$'){
                            flag = 1;
                        }else{
                            First[getNonTer(x)].insert(*it);
                        }
                    }
                    //沒有空串就不必再找下去了 
                    if(flag==0){
                        break; 
                    }else{
                        flag = 0;
                        tot++;
                    }
                }
                //如果右部所有符號的First集都有空串,則符號x的First集也有空串 
                if(tot==exp[i].right.length()){
                    First[getNonTer(x)].insert('$');
                }
            }
        }
    }
}

 

2.3.2 求Follow集的算法思想

  • 遍歷每一個右部包含非終結符x的產生式

  • 如果x的下一個字符是終結符,添加進x的Follow集

  • 如果x的下一個字符是非終結符,把該字符的First集加入x的Follow集(不能加入空串)

  • 如果下一字符的First集有空串並且該產生式的左部不是x,則把左部的Follow集加入x的Follow集

  • 如果x已經是產生式的末尾,則把左部的Follow集添加到x的Follow集里

//求出非終結符的Follow集 
void Gramma::getFollow(char x){
    //找到非終結符x出現的位置
    for(int i=0;i<cnt;i++){
        int index = -1;
        int len = exp[i].right.length();
        for(int j=0;j<len;j++){
            if(exp[i].right[j]==x){
                index = j;
                break;
            }
        }
        //如果找到了x,並且它不是最后一個字符 
        if(index!=-1&&index<len-1){
            //如果下一個字符是終結符,添加進x的Follow集 
            char next = exp[i].right[index+1];
            if(!isNotTer(next)){
                Follow[getNonTer(x)].insert(next);
            }else{
            //如果下一個字符是非終結符 
                bool flag = 0;
                set<char>::iterator it;
                //遍歷下一個字符的First集
                for(it = First[getNonTer(next)].begin();it!=First[getNonTer(next)].end();it++){
                    if(*it=='$'){
                        flag = 1;
                    }else{
                        Follow[getNonTer(x)].insert(*it);
                    }
                }
                //如果有空串並且左部不是它本身(防止陷入死循環),當前非終結符的Follow集是x的Follow集
                char tmp = exp[i].left; 
                if(flag&&tmp!=x){
                    getFollow(tmp);
                    set<char>::iterator it;
                    for(it = Follow[getNonTer(tmp)].begin();it!=Follow[getNonTer(tmp)].end();it++){
                        Follow[getNonTer(x)].insert(*it);
                    }
                }
            }
        }else if(index!=-1&&index==len-1&&x!=exp[i].left){
            //如果x在產生式的末尾,則產生式左部的Follow集應該添加到x的Follow集里 
            char tmp = exp[i].left; 
            getFollow(tmp);
            set<char>::iterator it;
            for(it = Follow[getNonTer(tmp)].begin();it!=Follow[getNonTer(tmp)].end();it++){
                Follow[getNonTer(x)].insert(*it);
            }
        }
    }
} 

    2.3.3 求預測分析表的算法思想

  • 遍歷每一個產生式
  • 如果右部的第一個字符tmp是終結符且不是空串,更新預測分析表,即table[left][tmp] = i(i為產生式編號)

  • 如果右部的第一個字符是空串,遍歷左部的Follow集,更新預測分析表,即table[left][x] = i(i為產生式編號,x為Follow集字符編號)

  • 如果右部的第一個字符是非終結符,遍歷它的First集,更新預測分析表,即table[left][x] = i(i為產生式編號,x為First集字符編號)

//獲得預測分析表
void AnalyzTable::getTable(){
    for(int i=0;i<cnt;i++){
        char tmp = exp[i].right[0];
        //如果產生式右部的第一個字符是終結符
        if(!isNotTer(tmp)){
            //該終結符不是空串,更新table
            if(tmp!='$'){
                detect[getNonTer(exp[i].left)][getTer(tmp)]++;
                table[getNonTer(exp[i].left)][getTer(tmp)] = i;
            }
            if(tmp=='$'){
                //該終結符是空串,遍歷左部的Follow集,更新table
                set<char>::iterator it;
                for(it = Follow[getNonTer(exp[i].left)].begin();it!=Follow[getNonTer(exp[i].left)].end();it++){
                    table[getNonTer(exp[i].left)][getTer(*it)] = i;
                    detect[getNonTer(exp[i].left)][getTer(*it)]++;
                }
            }
        }else{
            //如果產生式右部的第一個字符是非終結符,遍歷它的First集,更新table
            set<char>::iterator it;
            for(it = First[getNonTer(tmp)].begin();it!=First[getNonTer(tmp)].end();it++){
                table[getNonTer(exp[i].left)][getTer(*it)] = i;
                detect[getNonTer(exp[i].left)][getTer(*it)]++;
            }
            //如果有空串,遍歷左部的Follow集,更新table  
            if(First[getNonTer(tmp)].count('$')!=0){
                set<char>::iterator it;
                for(it = Follow[getNonTer(exp[i].left)].begin();it!=Follow[getNonTer(exp[i].left)].end();it++){
                    table[getNonTer(exp[i].left)][getTer(*it)] = i;
                    detect[getNonTer(exp[i].left)][getTer(*it)]++;
                }                
            }
        }
    }
}

    2.3.4 符號串的分析過程

  • 規定f1為符號棧棧頂字符,f2為剩余輸入串的第一個字符
  • 若f1=f2=“#”,則分析成功,停止分析

  • 若f1=f2≠”#”,則把f1從棧頂彈出,讓f2指向下一個輸入符號.

  • 若f1是一個非終結符,則查看分析表,若table[f1][f2]中有值,則f1出棧,並且產生式的右部反序進棧

  • 再把產生式的右部符號推進棧的同時應做這個產生式相應得語義動作,若M[A,a]中存放着”出錯標志”,則調用出錯診察程序error.

      

                        分析棧

                    流程圖

//分析符號串
void AnalyzTable::analyExp(string s){
    cout<<setw(15)<<"分析棧"<<setw(15)<<"剩余輸入串"<<setw(20)<<"推導式"<<endl;
    //把整個串倒序push進剩余符號vector
    left.push_back('#');
    for(int i=s.length()-1;i>=0;i--){
        left.push_back(s[i]);
    }
    //把#和開始符push進分析棧
    stack.push_back('#');
    stack.push_back(not_ter[0]);
    //如果剩余輸入串長度不為0,就一直循環
    while(left.size()>0){
        //輸出分析棧內容
        string outputs = "";
        for(int i=0;i<stack.size();i++){
            outputs+=stack[i];
        }
        cout<<setw(15)<<outputs;
        outputs = "";
        //輸出剩余輸入串內容
        for(int i=left.size()-1;i>=0;i--){
            outputs+=left[i];
        }
        cout<<setw(15)<<outputs;
        char f1 = stack[stack.size()-1];
        char f2 = left[left.size()-1];
        //如果可以匹配,並且都為# 
        if(f1==f2&&f1=='#'){
            cout<<setw(21)<<"Accept!"<<endl;
            return;
        }
        //如果可以匹配,並且都為終結符 
        if(f1==f2){
            stack.pop_back();
            left.pop_back();
            cout<<setw(15)<<f1<<" 匹配"<<endl;
        }else if(table[getNonTer(f1)][getTer(f2)]!=-1){
            //如果在預測分析表中有值
            int index = table[getNonTer(f1)][getTer(f2)];
            stack.pop_back();
            if(exp[index].right!="$"){
                for(int i=exp[index].right.length()-1;i>=0;i--){
                    stack.push_back(exp[index].right[i]);
                }
            }
            cout<<setw(15)<<exp[index].left<<"->"<<exp[index].right<<endl;
        }else{
            cout<<setw(15)<<"error"<<endl;
            return;
        }
    }
}

  2.4 樣例測試

 

三、基於遞歸子程序法的語法分析

    3.1 語法

      下面是一個簡單的四則運算語法規則

  • E:->TA

  • A->+TA | $

  • T->FB

  • B->*FB | $

  • F->(E) | i

    3.2 Select集

  1. { (, i }

  2. { + }

  3. { ), # }

  4. { (, i }

  5. { * }

  6. { +, ), #}

  7. { ( }

  8. { i }

    3.3 代碼以及測試

#include <cstring>
#include <cstdio>
#include <iostream>
#define MaxLength 10000
using namespace std;

//聲明函數
void A();
void B();
void F();
void T();
 
//字符串結構體
struct inputStr{
    string str;   //緩存字符串
    int pointer;  //字符串下表指針
}inputstr;
char token;       //存放當前比較的字符
int  flag=1;      //編譯成功標記

//獲取當前字符
char getToken(){
    token=inputstr.str[inputstr.pointer];
    inputstr.pointer++;
    return token;
}

//獲取字符串
void getSring(){
    cout<<"請輸入待匹配的字符串" 
     cin>>inputstr.str;
     inputstr.str+="#";
}

//錯誤處理  待完善 
void error(){
    flag=0;
    printf("error;");
}

//起始非終結符函數
void E(){
    token=getToken();
    if(token != '(' && token!='i')//如果下一個當前取得的字符不為(、i,則字符串錯誤
        error();
    else{
        if(token=='('){//如果為(,執行(),並取下一個字符
            E();
            token=getToken();
            if(token!=')')
                error();
        }
        B();
        A();
    }
}

//非終結符T判斷
void T(){
    token=getToken();
    if(token != '(' && token!='i')//如果下一個當前取得的字符不為(、i,則字符串錯誤
        error();
    else{
        if(token=='('){
            E();
            token=getToken();
            if(token!=')')
                error();
        }
        B();
    }
}

//非終結符F判斷
void F(){
    token=getToken();
    if(token != '(' && token!='i')
        error();
    else{
        if(token=='('){
            E();
            token=getToken();
            if(token!='(') error();
        }
        else{
            return;
        }
    }
}

//非終結符A判斷
void A(){
    token=getToken();
    //如果當前字符不符合,調用錯誤處理程序 
    if(token!='+'&&token!=')'&&token!='#')
        error();
    else{
        if(token=='+'){
            T();
            A();
        }else{
            //如果A終結符為空,字符串指針回退 
            inputstr.pointer--;
        }
    }
}

//非終結符B判斷
void B(){
    token=getToken();
    if(token!='*'&&token!='+'&&token!=')'&&token!='#')//如果當前字符不符合,輸出錯誤
        error();
    else{
        if(token=='*'){
            F();
            B();
        }else{//如果B終結符為空,字符串指針回溯一個
            inputstr.pointer--;
        }
    }
}
 
int main()
{
    inputstr.pointer=0;
    getSring();
    //執行初始非終結符
    E();
    if(flag==1){
        printf("success!");
    }
    else{
        printf("error");
    }
    return 0;
}

 

 


免責聲明!

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



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