用c語言手搓一個600行的類c語言解釋器: 給編程初學者的解釋器教程(4)- 語法分析1:EBNF和遞歸下降文法

用c語言手搓一個600行的類c語言解釋器: 給編程初學者的解釋器教程(4)- 語法分析1:EBNF和遞歸下降文法

用c語言手搓一個600行的類c語言解釋器: 給編程初學者的解釋器教程(1)- 目標和前言

用c語言手搓一個600行的類c語言解釋器: 給編程初學者的解釋器教程(2)- 簡介和設計

用c語言手搓一個600行的類c語言解釋器: 給編程初學者的解釋器教程(3)- 詞法分析

項目github地址及源碼:
https://github.com/yunwei37/tryC

這一章開始進入解釋器的核心部分: 語法分析器;

我們來看看兩個概念,EBNF和遞歸下降文法,以及如何用這兩個方法來計算tryC中的表達式。

基本概念

就像之前所說的那樣,語法分析指將詞法分析得到的標記流(token)進行分析,組成事先定義好的有意義的語句。那麼如何完成這樣一個工作呢?我們可以藉助一個叫“BNF”的數學工具。

BNF與上下文無關文法

Backus-Naur符號(就是衆所周知的BNF或Backus-Naur Form)是描述語言的形式化的數學方法,由John Backus (也許是Peter Naur)開發,最早用於描述Algol 60編程語言的語法。

BNF類似一種數學遊戲:從一個符號開始(叫做起始標誌,實例中常用S表示),然後給出替換前面符號的規則。

BNF語法定義的語言是一個字符串集合,可以按照下述規則書寫,這些規則叫做書寫規範(產生式規則),例如一個四則運算表達式可以表示爲:

exp -> exp op exp | ( exp ) | number
op -> + | - | * | /

其中’|'用於表示可選擇的不同項,"->"用於表示推導規則,從產生式左邊的符號可以推導出產生式右邊的符號;

要解析一個表達式,我們可以完成這樣一個替換:對於

(3+2)*4

可以替換爲

    exp => exp op exp => exp * number
        => ( exp ) * number
        => ( exp op exp ) * number
        => ( number + number ) * number

其中我們把能夠被替換的符號叫做非終結符,不能被替換的叫做終結符。

上下文無關文法就是說,這個文法中所有的產生式左邊只有一個非終結符,就像上面寫的那個文法一樣。通常我們在編譯器構建中使用的都是上下文無關文法。

EBNF

EBNF是基本巴科斯範式(BNF)元語法符號表示法的一種擴展,主要對BNF中常見的兩種情況,即重複項和可選項添加了相應的語法規則,如用方括號"[ … ]"
表示可選部分,用花括號"{ … }"表示重複出現的部分,如上面那個文法可以改寫爲:

exp -> exp { op exp } | ( exp ) | number
op -> + | - | * | /

又比如對於if語句可以寫成:

if-stmt -> if ( exp ) statement; [ else statement; ]

遞歸下降文法

遞歸下降分析法也很簡單,就是用函數的調用來模擬BNF的替換過程,我們只需要爲每個非終結符定義一個分解函數,它就能從起始非終結符開始,不斷地調用非終結符的分解函數,不斷地對非終結符進行分解,直到匹配輸入的終結符。

當然,遞歸下降分析並不是對於所有的文法都能正常使用的,例如經典的左遞歸問題:比如這樣一個文法

exp -> exp { op exp } | ( exp ) | number
op -> + | - | * | /

對於exp的替換需要調用exp的分解函數,而exp的分解函數一進來就調用它自身(即最左邊的符號),就會導致無限遞歸。這時就需要對文法進行改造。

實際上,EBNF文法就是爲了映射遞歸下降分析法的具體程序實現而設計的,因此我們這裏就用EBNF文法來實現遞歸下降分析。

來看看怎樣用遞歸下降文法計算tryC中的表達式

上面說了一大堆,現在看看實際的計算表達式的實現是怎樣的呢

算術表達式

tryC中需要計算四則運算表達式的EBNF文法如下:

exp -> term { addop term }
term -> factor { mulop factor }
factor -> number | ( exp )
addop -> + | -
mulop -> * | /

這裏對文法進行了一定的改造,讓它能夠正確表達四則運算的優先級,同時避免了左遞歸的問題,具體可以自己試着驗證一下。

tryC中算術表達式具體的代碼實現(就是上述文法直接轉換過來的啦):

double term() {
    double temp = factor();
    while (token == '*' || token == '/') {
        if (token == '*') {
            match('*');
            temp *= factor();
        }
        else {
            match('/');
            temp /= factor();
        }
    }
    return temp;
}

double factor() {
    double temp = 0;
    if (token == '(') {
        match('(');
        temp = expression();
        match(')');
    }
    else if(token == Num ||  token == Char){
        temp = token_val.val;
        match(Num);
    }
    else if (token == Sym) {
        temp = token_val.ptr->value;
        match(Sym);
    }
    else if (token == FuncSym) {
        return function();
    }
    else if (token == ArraySym) {
        symbol* ptr = token_val.ptr;
        match(ArraySym);
        match('[');
        int index = (int)expression();
        if (index >= 0 && index < ptr->value) {
            temp = ptr->pointer.list[index].value;
        }
        match(']');
    }
    return temp;
}

double expression() {
    double temp = term();
    while (token == '+' || token == '-') {
        if (token == '+') {
            match('+');
            temp += term();
        }
        else {
            match('-');
            temp -= term();
        }
    }
    return temp;
}

布爾表達式

tryC中同樣設計了布爾表達式:

tryC中需要計算四則運算表達式的EBNF文法如下:

boolOR = boolAND { '||' boolAND }
boolAND = boolexp { '||' boolexp }
boolexp -> exp boolop exp | ( boolOR ) | !boolexp
boolop -> > | < | >= | <= | ==

代碼實現:

int boolexp() {
    if (token == '(') {
        match('(');
        int result = boolOR();
        match(')');
        return result;
    }
    else if (token == '!') {
        match('!');
        return boolexp();
    }
    double temp = expression();
    if (token == '>') {
        match('>');
        return temp > expression();
    }
    else if (token == '<') {
        match('<');
        return temp < expression();
    }
    else if (token == GreatEqual) {
        match(GreatEqual);
        return temp >= expression();
    }
    else if (token == LessEqual) {
        match(LessEqual);
        return temp <= expression();
    }
    else if (token == Equal) {
        match(Equal);
        return temp == expression();
    }
    return 0;
}

int boolAND() {
    int val = boolexp();           
    while (token == AND) {
        match(AND);
        if (val == 0)    return 0;         // short cut
        val = val & boolexp();
        if (val == 0) return 0;
    }
    return val;
}

int boolOR() {
    int val = boolAND();
    while (token == OR) {
        match(OR);
        if (val == 1)    return 1;         // short cut
        val = val | boolAND();
    }
    return val;
}

一些重要概念回顧

  • 終結符/非終結符
  • BNF/EBNF
  • 上下文無關文法
  • 遞歸下降法

可對照源碼查看
https://github.com/yunwei37/tryC

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章