語義分析——TEST編譯器(3)


詞法分析: 詞法分析——TEST編譯器(1)
語法分析: 語法分析——TEST編譯器(2)

1 語義分析

1.1 功能

  • 進行語義檢查。對錶達式中的操作數進行類型的一致性檢查,例如誤把函數名誤當作變量名使用
  • 進行語義處理。對聲明語句和可執行語句的處理,其中聲明語句(int a ;)將變量名及其有關屬性填入符號表
  • 把可執行語句生成相應的中間代碼。

1.2 錯誤類型

  • 函數名重複定義
  • 變量名重複定義
  • 標識符沒有定義
  • call之後的標識符不是函數名
  • 賦值語句的標識符不是變量名
  • 運算語句的標識符不是變量名

1.3 中間代碼

用一個棧來保存中間代碼,其中包含每一個執行指令和操作數。類似彙編指令集合,使用時按順序一條條讀取,然後根據指令和操作數做出對應的動作。

1.4 符號表

在進行語義分析的過程中要使用符號表記錄每一個標識符的信息,符號表記錄了出現的每一個函數、變量的名字、地址、類型,根據這些信息從而可以判斷出一個標識符是否定義過、使用的類型對不對等等。而且記錄了標識符的地址,方便在虛擬機部分存儲變量位置。

1.5 設計思路

  • 思路就是在語法分析代碼的基礎上進行語義分析,在語法分析的同時,對每一個句子進行語義分析,所以當語法分析結束時語義分析也就結束了。如果當前句子沒問題就生產對應的中間代碼
  • 中間代碼中有部分指令需要操作數,而在按順序進行語義分析的時候有時候是無法直接確定操作數的內容的,這是就需要用到反填的操作。反填簡單說就是確定了當前指令在中間代碼集合中的位置,但是不知道其具體的操作數是多少,就先找個臨時變量記下這個指令的位置,等到可以確定的時候再根據這個臨時變量找到這個指令然後填入它的操作數。例如,BRF代表判斷條件爲假時就跳轉到操作數對應的指令位置,這個一般用在if…else裏,BRF就用在if的判斷之後,如果爲假就進入else語句,因爲中間代碼是按順序執行的,那麼在判斷條件之後就應該加上BRF,但此時還不知道要跳轉到哪個位置,所以要等if爲真的語句全部寫完之後,之後就輪到else語句了,只有到了這個時候才知道BRF的操作數應該填什麼,所以else語句開始的位置是BRF的操作數的值。
  • 在進行語義分析的過程中要時刻通過符號表對出現的標識符進行判斷,判斷類型是否正確、是否定義過等等。所以在考慮提升語義分析的效率時就要考慮如何對符號表進行搜索可以提高效率
  • 需要使用常數,但是常數是字符串類型:由於從文件中讀取二元組時,得到的都是字符串類型的數據,當需要用到常數時,就必須先將字符串數字轉換成整型數字。如果手動轉換的話會很麻煩,那麼可以使用atoi()函數,就會自動返回一個整型數字。但是需要注意的是,該函數是將字符串類型的數字轉換成整型的數字,而不能把其他非數字的字符串轉換成數字。

2 完整代碼

#include<bits/stdc++.h>
using namespace std ;
enum symbolType {function, variable};   //用枚舉類型方便比較類型
struct tree {       //語法樹
    string data ;
    vector<tree*> son ;
    tree* addSon(string str) {
        tree* newSon = new tree() ;
        newSon->data = str ;
        son.push_back(newSon) ;
        return newSon ;
    }
} *root;
struct symbol {              //符號表
    char name[20] ;         //標識符名字
    int address ;           //地址
    enum symbolType kind ;  //標識符的類型 函數名或變量名
};
struct code {                   //中間代碼
    char operationCode[10] ;    //操作碼
    int operand ;               //操作數
};

FILE *inputFile ;   //輸入文件
FILE *outputFile ;  //輸出文件
char inputAddress[20] ;     //輸入文件地址
char outputAddress[20] ;    //輸出文件地址
char type[20] ;     //單詞類型
char value[40] ;    //單詞值
int line = 0 ;      //當前讀到第幾行
int offset = 0 ;    //變量地址
int codeLength = 0 ;    //中間代碼長度
int symbolLength = 0 ;  //符號表數據個數
vector<symbol> symbolTable ;    //符號表
code codes[200] ;            //中間代碼

//存在函數之間相互調用,要先聲明函數
int parseAlanalysis() ;
int program() ;
int fun_declaration(tree* father) ;
int main_declaration(tree* father) ;
int function_body(tree* father) ;
int declaration_list(tree* father) ;
int declaration_stat(tree* father) ;
int statement_list(tree* father) ;
int statement(tree* father) ;
int if_stat(tree* father) ;
int while_stat(tree* father) ;
int for_stat(tree* father) ;
int read_stat(tree* father) ;
int write_stat(tree* father) ;
int compound_stat(tree* father) ;
int call_stat(tree* father) ;
int expression_stat(tree* father) ;
int expression(tree* father) ;
int bool_expr(tree* father) ;
int additive_expr(tree* father) ;
int term(tree* father) ;
int factor(tree* father) ;
int do_while_stat(tree* father) ;
void getNext() ;

//將當前數據存入語法樹中
void add(tree* p) {
    p->addSon(value) ;
}
//輸出語法樹
void printTree(tree* p, int x) {
    for(int i=0;i<x;i++)    //每次輸出之前都要先輸出一定的空格實現深度
        printf("|  ") ;
    printf("%s\n", p->data.c_str()) ;
	int num = p->son.size() ;
	if(num == 0)
        return ;
	for(int i=0; i<num; i++)    //輸出子內容
		printTree(p->son[i],x+1);   //深度加一
	return ;
}
//讀取下一行內容
void getNext() {
    fscanf(inputFile, "%s%s\n", type, value) ;
    printf("第%d行  %s   %s\n", line, type, value) ;  //輸出讀取到的當前行 有錯誤時便於定位錯誤
    line++ ;
}
//將標識符添加到符號表中
int insertSymbolTable(enum symbolType kind, char name[]) {
    symbol temp ;       //臨時變量,存要記錄的新符號
    //檢查是否存在重複定義
    switch (kind) {
        case function : {   //函數
            for(int i=0; i<symbolTable.size(); i++) {
                if((strcmp(name, symbolTable[i].name))==0 && (symbolTable[i].kind==function))
                    return 12 ;         //函數名重複定義
            }
            temp.kind = function ;      //沒有重複的函數就正常賦值
            break ;
        }
        case variable : {   //變量
            for(int i=0; i<symbolTable.size(); i++) {
                if((strcmp(name, symbolTable[i].name))==0 && (symbolTable[i].kind==variable))
                    return 13 ;         //變量名重複定義
            }
            temp.kind = variable ;
            temp.address = offset ;     //變量要有地址
            offset++ ;
            break ;
        }
    }
    strcpy(temp.name, name) ;
    symbolTable.push_back(temp) ;       //將新的符號添加到符號表中
    symbolLength++ ;
    return 0 ;
}
//查找標識符再符號表中的位置
int lookup(char name[], int *place) {
    for(int i=0; i<symbolTable.size(); i++) {
        if((strcmp(name, symbolTable[i].name)) == 0) {  //名字相同就找到
            *place = i ;     //找到就把位置賦值然後返回
            return 0 ;
        }
    }
    return 14 ;         //沒有找到說明沒有定義,報錯
}
//判斷條件
int judge(tree* father) {
    int flag = 0 ;
    //檢查 (
    getNext() ;
    if(strcmp("(", type) != 0)
        return 4 ;
    add(father) ;
    //檢查 expr
    getNext() ;
    flag = expression(father) ;     //判斷條件
    if(flag > 0)
        return flag ;
    //可能存在多重判斷語句
    while(strcmp("&&", type)==0 || strcmp("||", type)==0) {
        add(father) ;
        //檢查 expr
        getNext() ;
        flag = expression(father) ;     //判斷條件
    }
    //檢查 )
    if(strcmp(")", type) != 0)
        return 5 ;
    add(father) ;
    return flag ;
}
//運算因子
int factor(tree* father) {     // < factor >::=‘(’ < additive_expr >‘)’|ID|NUM
    tree* current = father->addSon("<factor>") ;
    int flag = 0 ;
    //括號有優先級,需要先處理其中的算術表達式
    if(strcmp("(", type) == 0) {
        add(current) ;
        getNext() ;
        flag = additive_expr(current) ;
        if(flag > 0)
            return flag ;
        if(strcmp(")", type) != 0)
            return 5 ;
        add(current) ;
        getNext() ;
    }
    else {  //因子還可能是標識符或者常數,都是最小單位了,沒問題就直接返回
        if(strcmp("ID", type)==0) {
            int place ;
            flag = lookup(value, &place) ;
            if(flag > 0)
                return flag ;
            if(symbolTable[place].kind != variable) //運算語句中的標識符要是
                return 17 ;
            strcpy(codes[codeLength].operationCode, "LOAD") ;       //將變量存入
            codes[codeLength].operand = symbolTable[place].address ;    //將要使用的變量地址存入
            codeLength++ ;
            add(current) ;
            getNext() ;
            return flag ;
        }
        else if(strcmp("NUM", type)==0) {
            strcpy(codes[codeLength].operationCode, "LOADI") ;       //將變量存入
            codes[codeLength].operand = atoi(value) ;//由於從文件讀取出來的是字符串,需要把字符串類型的數字轉換成整型
            codeLength++ ;
            add(current) ;
            getNext() ;
            return flag ;
        }
        else
            return 7 ;
    }
    return flag ;
}
//運算項
int term(tree* father) {    //< term >::=<factor>{(*| /)< factor >}
    tree* current = father->addSon("<term>") ;
    int flag = 0 ;
    //檢查 term
    flag = factor(current) ;
    if(flag > 0)
        return flag ;
    //之後可能有多個運算過程所以要用循環不能用if
    while(strcmp("*", type)==0 || strcmp("/", type)==0) {
        char temp[20] ;     //暫時存儲是什麼類型的運算符,之後沒有問題就根據對應的類型填寫中間代碼
        strcpy(temp, type) ;
        add(current) ;
        //檢查 term
        getNext() ;
        flag = factor(current) ;
        if(flag > 0)
            return flag ;
        //將對應的運算符加入到中間代碼中
        if(strcmp("*", temp) == 0)
            strcpy(codes[codeLength++].operationCode, "MULT") ;       //乘
        else if(strcmp("/", temp) == 0)
            strcpy(codes[codeLength++].operationCode, "DIV") ;       //除
    }
    return flag ;
}
//算數表達式
int additive_expr(tree* father) {   // < additive_expr>::=<term>{(+|-)< term >}
    tree* current = father->addSon("<additive_expr>") ;
    int flag = 0 ;
    //檢查 term
    flag = term(current) ;
    if(flag > 0)
        return flag ;
    //之後可能有多個運算過程所以要用循環不能用if
    while(strcmp("+", type)==0 || strcmp("-", type)==0) {
        char temp[20] ;     //暫時存儲是什麼類型的運算符,之後沒有問題就根據對應的類型填寫中間代碼
        strcpy(temp, type) ;
        add(current) ;
        //檢查 term
        getNext() ;
        flag = term(current) ;
        if(flag > 0)
            return flag ;
        //將對應的運算符加入到中間代碼中
        if(strcmp("+", temp) == 0)
            strcpy(codes[codeLength++].operationCode, "ADD") ;       //加
        else if(strcmp("-", temp) == 0)
            strcpy(codes[codeLength++].operationCode, "SUB") ;       //減
    }
    return flag ;
}
//布爾表達式
int bool_expr(tree* father) {   //<bool_expr>::=<additive_expr>|<additive_expr>(>|<|>=|<=|==|!=)<additive_expr>
                    //  <bool_expr>::=<additive_expr>{(>|<|>=|<=|==|!=)<additive_expr>}
    tree* current = father->addSon("<bool_expr>") ;
    int flag = 0 ;
    //檢查 additive_expr
    flag = additive_expr(current) ;
    if(flag > 0)
        return flag ;
    //之後可能有各種關係運算符,有就繼續判斷,沒有就結束
    if(strcmp(">", type)==0 || strcmp("<", type)==0 || strcmp(">=", type)==0 ||
       strcmp("<=", type)==0 || strcmp("==", type)==0 || strcmp("!=", type)==0) {
        char temp[20] ;     //暫時存儲是什麼類型的運算符,之後沒有問題就根據對應的類型填寫中間代碼
        strcpy(temp, type) ;
        add(current) ;
        //檢查 additive_expr
        getNext() ;
        flag = additive_expr(current) ;
        if(flag > 0)
            return flag ;
        //將對應的運算符加入到中間代碼中
        if(strcmp(">", temp) == 0)
            strcpy(codes[codeLength++].operationCode, "GT") ;       //大於
        else if(strcmp(">=", temp) == 0)
            strcpy(codes[codeLength++].operationCode, "GE") ;       //大於等於
        else if(strcmp("<", temp) == 0)
            strcpy(codes[codeLength++].operationCode, "LES") ;      //小於
        else if(strcmp("<=", temp) == 0)
            strcpy(codes[codeLength++].operationCode, "LE") ;       //小於等於
        else if(strcmp("==", temp) == 0)
            strcpy(codes[codeLength++].operationCode, "EQ") ;       //等於
        else if(strcmp("!=", temp) == 0)
            strcpy(codes[codeLength++].operationCode, "NOTEQ") ;    //不等於
    }
    return flag ;
}
//表達式
int expression(tree* father) {  // < expression >::= ID=<bool_expr>|<bool_expr>
    tree* current = father->addSon("<expression>") ;
    int flag = 0 ;
    int filePlace ;     //記錄當前文件位置
    int place ;
    char t1[40], t2[40] ;   //臨時存取數據
    //此處進來的數據是ID
    if(strcmp("ID", type) == 0) {   //< expression >::= ID=<bool_expr>
        //add(current) ;
        filePlace = ftell(inputFile) ;  //進來數據在文件中的數據
        //檢查 =
        fscanf(inputFile, "%s%s\n", t1, t2) ;   //首先把數據存到臨時的數組中
        line++ ;
        if(strcmp("=", t1) == 0) {    //該表達式例如 a=xxxx賦值
            flag = lookup(value, &place) ;
            if(flag > 0)
                return flag ;
            if(symbolTable[place].kind != variable) //賦值語句只能對變量進行
                return 16 ;
            printf("第%d行  %s   %s\n", line-1, t1, t2) ;
            add(current) ;
            current->addSon(t1) ;
            //檢查 bool_expr
            getNext() ;   //如果進入到此處就繼續往下讀取數據就可以了,之後數據還是存在原來的數組中
            flag = bool_expr(current) ;
            if(flag > 0)
                return flag ;
            strcpy(codes[codeLength].operationCode, "STO") ;     //將賦值內容存入變量中
            codes[codeLength].operand = symbolTable[place].address ;
            codeLength++ ;
        }
        else {                      //該表達式例如 a>=xxxx
            line-- ;            //多讀取的行數要減掉
            fseek(inputFile, filePlace, 0) ;  //讀取數據返回到 = 前的標識符,即剛進來的ID的位置
            flag = bool_expr(current) ;
            if(flag > 0)
                return flag ;
        }
    }
    else        //< expression >::= <bool_expr>
        flag = bool_expr(current) ;
    return flag ;
}
//表達式序列
int expression_stat(tree* father) {     //<expression_stat>::=< expression >;|;
    tree* current = father->addSon("<expression_stat>") ;
    //add(current) ;
    int flag = 0 ;
    //檢查第一個是 ;
    if(strcmp(";", type) == 0) {
        add(current) ;
        getNext() ;
        return flag ;
    }
    //檢查expression
    flag = expression(current) ;
    if(flag > 0)
        return flag ;
    //檢查末尾的 ;
    if(strcmp(";", type) == 0) {
        add(current) ;
        getNext() ;
        return 0 ;
    }
    else            //缺少 ;
        return 3 ;
}
//調用函數語句
int call_stat(tree* father) {   // < call _stat>::= call ID‘(’ ‘)’ ;
    int flag = 0 ;
    int place ;
    tree* current = father->addSon("<call _stat>") ;
    add(current) ;
    //檢查 ID
    getNext() ;

    if(strcmp("ID", type) != 0)
        return 6 ;
    add(current) ;
    flag = lookup(value, &place) ;
    if(flag > 0)
        return flag ;
    if(symbolTable[place].kind != function) { //call之後不是函數名屬於錯誤
        flag = 15 ;
		return flag ;
	}
    //檢查 (
    getNext() ;
    if(strcmp("(", type) != 0)
        return 4 ;
    add(current) ;
    getNext() ;
    //可能帶有參數
    while(strcmp("NUM", type)==0 || strcmp("ID", type)==0) {   //call xxx(yyy,....)
        add(current) ;
        //檢查是否存在多個參數
        getNext() ;
        if(strcmp(",", type) == 0) {       //有逗號說明有多個參數
            add(current) ;
            getNext() ;
            if(strcmp("NUM", type)!=0 && strcmp("ID", type)!=0)  //如果逗號之後不是新的參數就出錯
                return 10 ;
        }
        else            //沒有參數了就結束
            break ;
    }
    //檢查 )
    if(strcmp(")", type) != 0)
        return 5 ;
    add(current) ;
    getNext() ;
    //檢查 ;
    if(strcmp(";", type) != 0)
        return 3 ;
    add(current) ;
    strcpy(codes[codeLength].operationCode, "CAL") ;     //調用函數
    codes[codeLength].operand = symbolTable[place].address ;     //進入函數的開始位置
    codeLength++ ;
    getNext() ;
    return 0 ;  //沒有問題就返回0
}
//複合語句
int compound_stat(tree* father) {   // <compound_stat>::=’{‘<statement_list>’}‘
    tree* current = father->addSon("<compound_stat>") ;
    add(current) ;
    int flag = 0 ;
    getNext() ;
    flag = statement_list(current) ;
    if(flag > 0)
        return flag ;
    add(current) ;
    getNext() ;
    return flag ;   //不管是否正確直接返回, } 在statement_list已經判斷了
}
//write語句
int write_stat(tree* father) {  // <write_stat>::=write <expression>;
    tree* current = father->addSon("<write_stat>") ;
    add(current) ;
    int flag = 0 ;
    //檢查 expression
    getNext() ;
    flag = expression(current) ;
    if(flag > 0)
        return flag ;
    //檢查 ;
    if(strcmp(";", type) != 0)
        return 3 ;
    add(current) ;
    strcpy(codes[codeLength].operationCode, "OUT") ;     //輸出
    codeLength++ ;
    getNext() ;
    return 0 ;
}
//read語句
int read_stat(tree* father) {   //<read_stat>::=read ID;
    int place ;
    int flag ;
    tree* current = father->addSon("<read_stat>") ;
    add(current) ;
    //檢查 ID
    getNext() ;
    if(strcmp("ID", type) != 0)
        return 6 ;
    flag = lookup(value, &place) ;       //在讀取之前要先檢查一下該變量是否被定義
    if(flag > 0)
        return flag ;
    strcpy(codes[codeLength].operationCode, "IN") ;     //輸入
    codeLength++ ;
    strcpy(codes[codeLength].operationCode, "STO") ;    //將輸入的值存入對應變量
    codes[codeLength].operand = symbolTable[place].address ;    //存入指定地址的變量中
    codeLength++ ;
    add(current) ;
    //檢查 ;
    getNext() ;
    if(strcmp(";", type) != 0)
        return 3 ;
    add(current) ;
    getNext() ;     //讀取下一個語句的內容
    return 0 ;
}
//for語句
int for_stat(tree* father) {    // <for_stat>::= for’(‘<expr>;<expr>;<expr>’)’<statement>
    tree* current = father->addSon("<for_stat>") ;
    add(current) ;
    int flag = 0 ;
    int cx1, cx2, entance1, entance2 ;
    //檢查 (
    getNext() ;
    if(strcmp("(", type) != 0)
        return 4 ;
    add(current) ;
    //檢查 expr1
    getNext() ;
    flag = expression(current) ;     //判斷條件
    if(flag > 0)
        return flag ;
    //檢查 ;
    if(strcmp(";", type) != 0)
        return 3 ;
    add(current) ;
    entance1 = codeLength ;         //記錄下判斷語句的入口,每次循環完都要回到此處
    //檢查 expr2
    getNext() ;
    flag = expression(current) ;     //判斷條件
    if(flag > 0)
        return flag ;
    //檢查 ;
    if(strcmp(";", type) != 0)
        return 3 ;
    add(current) ;
    strcpy(codes[codeLength].operationCode, "BFR") ;    //判斷爲假就結束for
    cx1 = codeLength ;          //暫時不知道結束位置,等待反填
    codeLength++ ;
    strcpy(codes[codeLength].operationCode, "BR") ;    //判斷爲真就進入循環語句
    cx2 = codeLength ;          //暫時不知道語句開始位置,等待反填
    codeLength++ ;
    //檢查 expr3
    getNext() ;
    entance2 = codeLength ;         //記錄條件3的入口,用於語句的反填
    flag = expression(current) ;     //判斷條件
    if(flag > 0)
        return flag ;
    //檢查 )
    if(strcmp(")", type) != 0)
        return 5 ;
    add(current) ;
    strcpy(codes[codeLength].operationCode, "BR") ;    //執行完就進入判斷
    codes[codeLength].operand = entance1 ;      //跳轉的位置是判斷語句的入口
    codeLength++ ;
    //檢查 statement
    getNext() ;
    codes[cx2].operand = codeLength ;       //反填判斷爲真的跳轉入口
    flag = statement(current) ;             //for中的語句
    strcpy(codes[codeLength].operationCode, "BR") ;    //執行完就進入條件3
    codes[codeLength].operand = entance2 ;             //跳轉的位置是條件3的入口
    codeLength++ ;
    codes[cx1].operand = codeLength ;       //反填結束的跳轉入口
    return flag ;           //最後一個語句,不管是否正確都直接輸出即可
}
//do_while語句
int do_while_stat(tree* father) {    // do ‘{’ < statement > ‘}’ while ‘(‘<expr >’)’ ;
    tree* current = father->addSon("<do_while_stat>") ;
    add(current) ;
    int flag = 0 ;
    int cx, entance ;
    //檢查 {
    getNext() ;
    if(strcmp("{", type) != 0)
        return 1 ;
    entance = codeLength ;      //執行語句的入口
    //檢查執行語句
    flag = statement(current) ; //檢查完執行語句後 } 已經檢查完了
    if(flag > 0)
        return flag ;
    strcpy(codes[codeLength].operationCode, "BF") ;    //執行完語句就進入判斷語句
    codes[codeLength].operand = codeLength + 1 ;        //判斷語句就是下一句
    codeLength++ ;
    //檢查 while
    if(strcmp("while", type) != 0)
        return 11 ;
    add(current) ;
    //檢查判斷語句
    flag = judge(current) ;
    if(flag > 0)
        return flag ;
    strcpy(codes[codeLength].operationCode, "BRF") ;    //爲假就退出循環
    cx = codeLength ;                      //暫時不知道循環退出位置,等待反填
    codeLength++ ;
    strcpy(codes[codeLength].operationCode, "BR") ;     //爲真就返回執行語句
    codes[codeLength].operand = entance ;           //返回執行語句入口
    codeLength++ ;
    codes[cx].operand = codeLength ;           //此處是循環出口,反填
    getNext() ;
    return flag ;
}
//while語句
int while_stat(tree* father) {      //<while_stat>::= while ‘(‘<expr >’)’ < statement >
    int flag = 0 ;
    int cx, entance ;
    tree* current = father->addSon("<while_stat>") ;
    add(current) ;
    entance = codeLength ;      //每次循環都要重新判斷條件,所以要記錄判斷條件的入口
    flag = judge(current) ;
    if(flag > 0)
        return flag ;
    strcpy(codes[codeLength].operationCode, "BRF") ;    //爲假就退出whilie
    cx = codeLength ;       //反填結束時的位置
    codeLength++ ;
    //檢查 statement
    getNext() ;
    flag = statement(current) ;    //  while中的語句
    if(flag > 0)
        return flag ;
    strcpy(codes[codeLength].operationCode, "BR") ;    //語句結束後要重新回到判斷
    codes[codeLength].operand = entance ;       //回到判斷起點
    codeLength++ ;
    codes[cx].operand = codeLength ;        //反填結束跳轉的位置
    return flag ;           //最後一個語句,不管是否正確都直接輸出即可
}
//if語句
int if_stat(tree* father) {     // <if_stat>::= if ‘(‘<expr>’)’ <statement > [else < statement >]
    int flag = 0 ;
    int cx1, cx2 ;
    tree* current = father->addSon("<if_stat>") ;
    add(current) ;
    flag = judge(current) ;     //判斷條件
    if(flag > 0)
        return flag ;
    strcpy(codes[codeLength].operationCode, "BRF") ;    //爲假就進入else
    cx1 = codeLength ;      //記錄if爲假時結束跳轉的地方,之後反填
    codeLength++ ;
    //檢查 statement
    getNext() ;
    flag = statement(current) ;    //  if中的語句
    if(flag > 0)
        return flag ;
    strcpy(codes[codeLength].operationCode, "BR") ;     //爲真結束之後退出if
    cx2 = codeLength ;      //記錄if結束時跳轉的地方,之後反填
    codeLength++ ;
    codes[cx1].operand = codeLength ;   //if爲假要跳轉到else的開始語句
    //檢查 else
    if(strcmp("else", type) == 0) {   //當前的數據由上一步中獲得了
        add(current) ;
        //檢查 statement
        getNext() ;
        flag = statement(current) ;    //else中的語句
        if(flag > 0)
            return flag ;
    }
    codes[cx2].operand = codeLength ;   //if結束時跳轉的地方
    return flag ;
}
//語句內容
int statement(tree* father) {
/*<statement>::=<if_stat>|<while_stat>|<for_stat>|<read_stat>|<write_stat>|
<compound_stat> |<expression_stat> | < call _stat> */
    tree* current = father->addSon("<statement>") ;
    int flag = 0 ;
    //從聲明中結束後得到第一個語句單詞
    if(flag==0 && strcmp("if", type)==0)
        flag = if_stat(current) ;
    if(flag==0 && strcmp("while", type)==0)
        flag = while_stat(current) ;
    if(flag==0 && strcmp("for", type)==0)
        flag = for_stat(current) ;
    if(flag==0 && strcmp("read", type)==0)
        flag = read_stat(current) ;
    if(flag==0 && strcmp("write", type)==0)
        flag = write_stat(current) ;
    if(flag==0 && strcmp("{", type)==0)    //複合語句要從 { 開始
        flag = compound_stat(current) ;
    if(flag==0 && strcmp("call", type)==0)
        flag = call_stat(current) ;
    if(flag==0 && (strcmp("(", type)==0 || strcmp(";", type)==0 ||
                strcmp("NUM", type)==0 || strcmp("ID", type)==0) )
        flag = expression_stat(current) ;
    if(flag==0 && strcmp("do", type)==0)    //do_while語句
        flag = do_while_stat(current) ;
    return flag ;
}
//語句序列
int statement_list(tree* father) {  //<statement_list>::=<statement_list><statement>| ε
                                    //<statement_list>::={<statement>}
    tree* current = father->addSon("<statement_list>") ;
    int flag = 0 ;
    while(strcmp("}", type) != 0) { //到 } 結束
        if(feof(inputFile))     //一直到末尾都沒有 } 則出錯
            return 2 ;
        flag = statement(current) ;    //查看具體的語句
        if(flag > 0)
            return flag ;
    }
    return flag ;
}
//聲明內容
int declaration_stat(tree* father) {        //<declaration_stat>::=int ID;
    int flag = 0 ;
    tree* current = father->addSon("<declaration_stat>") ;
    add(current) ;
    //檢查 ID
    getNext() ;
    if(strcmp("ID", type) != 0)   //缺少標識符
        return 6 ;
    add(current) ;
    flag = insertSymbolTable(variable, value) ;    //將變量名添加到符號表中
    if(flag > 0)
        return flag ;
    //檢查 賦值
    getNext() ;
    if(strcmp("=", type) == 0) {    //變量名之後是 = 則需要賦值
        add(current) ;
        //檢查 賦值
        getNext() ;
        if(strcmp("NUM", type) != 0)    //缺少賦值
            return 7 ;
        add(current) ;
        //檢查 ;
        getNext() ;
        if(strcmp(";", type) != 0)   //缺少 ;
            return 3 ;
        add(current) ;
    }
    else {  //檢查 ;
        if(strcmp(";", type) != 0)   //缺少 ;
            return 3 ;
        add(current) ;
    }
    return 0 ;              //正常就返回0
}
//聲明序列
int declaration_list(tree* father) {    //<declaration_list>::=<declaration_list><declaration_stat> |ε
                                        //<declaration_list>::={<declaration_stat>}
    tree* current = father->addSon("<declaration_list>") ;
    int flag = 0 ;
    while(1) {
        getNext() ;
        if(strcmp("int", type) != 0)   //不是int說明已經沒有變量聲明瞭進入語句內容
            break ;
        flag = declaration_stat(current) ;     //具體的聲明內容
        if(flag > 0)
            return flag ;
    }
    return flag ;
}
//函數體
int function_body(tree* father) {       //<function_body>::= ‘{’<declaration_list><statement_list> ‘}’ (
    tree* current = father->addSon("<function_body>") ;
    int flag = 0 ;
    //檢查 {
    getNext() ;
    if(strcmp("{", type) != 0)   //缺少 {
        return 1 ;
    add(current) ;
    //檢查 declaration_list
    offset = 2 ;        //每次進入一個函數體的時候,相對地址都要重置爲2
    symbolTable[symbolLength-1].address = codeLength ;  //函數體的入口地址,上一個存在符號表中的是函數名
    flag = declaration_list(current) ; //如果成功了當前的單詞是語句的內容
    if(flag > 0)
        return flag ;
    strcpy(codes[codeLength].operationCode, "ENTER") ;  //爲函數調用開闢空間
    codes[codeLength].operand = offset ;
    codeLength++ ;
    //檢查 statement_list
    flag = statement_list(current) ;   //一開始不用讀取新的內容,成功了當前的單詞應該是 }
    if(flag > 0)
        return flag ;
    strcpy(codes[codeLength].operationCode, "RETURN") ;  //函數返回
    codeLength++ ;
    add(current) ;
    getNext() ;
    return flag ;
}
//主函數
int main_declaration(tree* father) {    //<main_declaration>::=main’(‘ ‘ )’ < function_body>
    int flag = 0 ;
    tree* current = father->addSon("<main_declaration>") ;
    add(current) ;
    getNext() ;
    //檢查 main
    if(strcmp("main", value) != 0)   //程序中最後的聲明必須是名字爲 main 的主函數
        return 6 ;
    add(current) ;
    flag = insertSymbolTable(function, value) ;    //將函數名添加到符號表中
    if(flag > 0)
        return flag ;
    //檢查 (
    getNext() ;
    if(strcmp("(", type) != 0)   //缺少 (
        return 4 ;
    add(current) ;
    //檢查 )
    getNext() ;
    if(strcmp(")", type) != 0)   //缺少 )
        return 5 ;
    add(current) ;
    codes[0].operand = codeLength ;     //main函數爲程序運行的起點
    //檢查 function_body
    return function_body(current) ;     //檢查函數體
}
//函數
int fun_declaration(tree* father) {     // <fun_declaration>::= function ID’(‘ ‘ )’< function_body>
    int flag = 0 ;
    tree* current = father->addSon("<fun_declaration>") ;   //當前類型
    add(current) ;              //當前數據沒問題就將其加入到樹中
    //檢查 ID
    getNext() ;
    if(strcmp("ID", type) != 0)   //缺少標識符代表函數名
        return 6 ;
    add(current) ;
    flag = insertSymbolTable(function, value) ;    //將函數名添加到符號表中
    if(flag > 0)
        return flag ;
    //檢查 (
    getNext() ;
    if(strcmp("(", type) != 0)   //缺少 (
        return 4 ;
    add(current) ;
    //檢查參數
    getNext() ;
    while(strcmp("int", type) == 0) {   //int xxx (,....)
        add(current) ;
        //檢查 標識符
        getNext() ;
        if(strcmp("ID", type) != 0)
            return 6 ;
        add(current) ;
        //檢查是否存在多個參數
        getNext() ;
        if(strcmp(",", type) == 0) {       //有逗號說明有多個參數
            add(current) ;
            getNext() ;
            if(strcmp("int", type) != 0)    //如果逗號之後不是新的參數就出錯
                return 10 ;
        }
        else            //沒有參數了就結束
            break ;
    }
    //檢查 )
    if(strcmp(")", type) != 0)   //缺少 )
        return 5 ;
    add(current) ;
    //檢查 function_body
    return function_body(current) ;     //檢查函數體
}
//程序
int program() {     //<program> ::={fun_declaration }<main_declaration>
    int flag = 0 ;
    strcpy(codes[codeLength].operationCode, "BR") ;    //程序開始先跳轉到main函數
    codeLength++ ;
    root = new tree() ;
    root->data = "<program>" ;
    //檢查 全局變量,可能有也可能沒有
    flag = declaration_list(root) ; //如果成功了當前的單詞是語句的內容
    if(flag > 0)
        return flag ;
    //檢查 fun_declaration
    while(strcmp("function", type) == 0) {   //函數聲明,可能有也可能沒有
        flag = fun_declaration(root) ;     //檢查函數聲明
        if(flag > 0)
            return flag ;               //若函數有問題就返回錯誤類型
    }
    codes[0].operand = codeLength ;
    if(strcmp("int", type) == 0) {  //主函數
        //檢查 main_declaration
        flag = main_declaration(root) ; //檢查主函數內容
        if(flag > 0)
            return flag ;
    }
    else
        return 6 ;
    return flag ;
}
//語法分析
int parseAlanalysis() {
    //初始化,得到輸入輸出文件
    printf("請輸入 輸入文件地址:") ;
    scanf("%s", inputAddress) ;
    if((inputFile=fopen(inputAddress, "r")) == NULL)     //讀取輸入文件
        return 8 ;      //讀取文件出錯就返回對應錯誤類型
    printf("請輸入 輸出文件地址:") ;
    scanf("%s", outputAddress) ;
    if((outputFile=fopen(outputAddress, "w")) == NULL)   //讀取輸出文件
        return 9 ;      //讀取文件出錯就返回對應錯誤類型
    printf("\n") ;
    return program() ;      //文件讀取成功就看看裏面的內容
}

int main() {
    int flag = 0 ;
    flag = parseAlanalysis() ;      //遞歸下降語法分析
    //處理結果
    printf("\n\n==================================\n\n") ;
    if(flag == 0) {
        printf("語義分析成功\n") ;
        //printTree(root, 0) ;        //輸出語法樹
        for(int i=0; i<codeLength; i++)     //將中間代碼寫入文件
            fprintf(outputFile, "%s    %d\n", codes[i].operationCode, codes[i].operand) ;
        printf("\n符號表\n") ;
        for(int i=0; i<symbolTable.size(); i++)
            printf("name: %s   address:%d   kind:%d \n",
                   symbolTable[i].name, symbolTable[i].address, symbolTable[i].kind) ;
        printf("\n") ;
        printf("中間代碼\n") ;
        for(int i=0; i<codeLength; i++)
            printf("%d 操作碼:%s   操作數:%d\n", i, codes[i].operationCode, codes[i].operand) ;
    }
    else {
        printf("語義分析失敗\n") ;
        switch (flag) {                 //選則具體的錯誤類型
            case 1 : printf("第%d行缺少 { \n", line-1) ; break ;
            case 2 : printf("第%d行缺少 } \n", line-1) ; break ;
            case 3 : printf("第%d行缺少 ; \n", line-1) ; break ;
            case 4 : printf("第%d行缺少 ( \n", line-1) ; break ;
            case 5 : printf("第%d行缺少 ) \n", line-1) ; break ;
            case 6 : printf("第%d行缺少標識符\n", line-1) ; break ;
            case 7 : printf("第%d行缺少操作數\n", line-1) ; break ;
            case 8 : printf("無法打開輸入文件%s\n", inputAddress);  break ;
            case 9 : printf("無法打開輸出文件%s\n", outputAddress); break ;
            case 10 : printf("第%d行缺少參數\n", line-1);  break ;
            case 11 : printf("第%d行缺少保留字\n", line-1);  break ;
            case 12 : printf("第%d行函數名 %s 重複定義\n", line-1, value);  break ;
            case 13 : printf("第%d行變量名 %s 重複定義\n", line-1, value);  break ;
            case 14 : printf("第%d行標識符 %s 沒有定義\n", line-1, value);  break ;
            case 15 : printf("第%d行call之後的標識符 %s 不是函數名\n", line-1, value);  break ;
            case 16 : printf("第%d行賦值語句的標識符 %s 不是變量名\n", line-1, value);  break ;
            case 17 : printf("第%d行運算語句的標識符 %s 不是變量名\n", line-1, value);  break ;
        }
    }
    return 0 ;
}

3 總結

  • 在寫語義分析的代碼時,需要填寫符號表,有些部分在課上老師講過,所以寫起來很輕鬆,但是有一部分內容需要自己考慮,最開始什麼都沒考慮就直接寫,結果邏輯上一直出問題,反覆改了很多次才完成。這時纔想起來在課上是進行了模擬,知道了每一步是什麼,什麼時候進行反填等等。於是在之後的打代碼過程中,我都是先在紙上畫出流程圖然後手寫了一遍過程,對整個填寫符號表的過程有了深刻理解之後再來打代碼就變得很輕鬆了。所以,先理解再執行是很高效的方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章