編譯原理入門課:(二)遞歸解析中怎麼處理運算符優先級

原文地址:蘋果梨的博客html

今天要給咱們的「計算器」加上乘、除和取模三種運算,而且加上對括號的優先級處理。git

若是不是採用遞歸方式解析表達式的話,能夠參考下調度場算法,這是一個利用隊列和堆棧來解決計算優先級的經典算法。github

用遞歸方式解析的話,只要深入理解了上一章的知識,這一章的都是小意思,那麼咱們開始。算法

產生式的優先級

首先咱們列出乘除法的文法:bash

term -> term '*' num | term '/' num | num
num  -> '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
複製代碼

這裏的term是項的意思,是指在加減法裏運算符左右的兩個項,先不要糾結具體是什麼意思,一會就會用到了。函數

咱們以幾個例子來人肉分析一下,這個文法和加減法的文法怎麼結合,才能得到正確的優先級:spa

expr(1+2*3) = num(1) '+' term(2*3)
expr(1*2+3) = term(1*2) '+' num(3)
expr(1*2+3*4) = term(1*2) '+' term(3*4)
複製代碼

而在上面乘除法的文法裏能夠看到,實際上term是能夠推導爲num的,因此上述例子又能夠變成:指針

expr(1+2*3) = term(1) '+' term(2*3)
expr(1*2+3) = term(1*2) '+' term(3)
expr(1*2+3*4) = term(1*2) '+' term(3*4)
複製代碼

是否是寫到這裏就豁然開朗了,若是乘除法的優先級更高,則讓乘除法的解析先進行,乘除法的結果當作加減法的項就能夠了。在文法裏的表現就是,優先級越高的產生式會越靠後,結果是這樣:code

expr -> expr '+' term | expr '-' term | term
term -> term '*' num | term '/' num | num
num  -> '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
複製代碼

具體的驗證你們本身試試就能夠了,絕對能夠按照正確的優先級進行解析。htm

前一章代碼的隱患

文法已經準備好了,原本能夠開始寫遞歸邏輯了,可是咱們得先解決前一章代碼的隱患。先看看以前的代碼:

int expr(const char *expStr)
{
    解析number,讀取expStr下一個字符
    while(expStr是加減號) {
        解析加減號,讀取expStr下一個字符
        解析number,讀取expStr下一個字符
    }
}
複製代碼

看上去邏輯是沒什麼問題的,可是這是由於number確定是單個字符的,若是按照以前的代碼繼續實現本章的文法,就會獲得這樣的難題:

int expr(const char *expStr)
{
    解析term,該讀取expStr後的第幾個字符?
    while(expStr是加減號) {
        解析加減號,讀取expStr下一個字符
        解析term,該讀取expStr後的第幾個字符?
    }
}
複製代碼

因此將字符串指針後移的操做,應該交給解析了字符或者說消化掉字符的角色來作,那麼咱們要用指針的指針來先改進一下上一章的代碼:

int number(const char **expStr) {
    int result = **expStr - '0';
    (*expStr)++;
    return result;
}

int expr(const char **expStr) {
    int result = number(expStr);
    while (**expStr == '+' || **expStr == '-') {
        char op = **expStr;
        (*expStr)++;
        if (op == '+') {
            result += number(expStr);
        } else {
            result -= number(expStr);
        }
    }
    return result;
}
複製代碼

實現乘除法和取餘

你們有了文法,也掌握了消除左遞歸的方法,還知道怎麼轉換成遞歸代碼,我感受其實都不太須要把代碼給你們貼出來了。😂

乘除法的實現思路和上一章加減法的思路徹底一致,咱們順帶把取餘也加上就行了:

int term(const char **expStr) {
    int result = number(expStr);
    while (**expStr == '*' || **expStr == '/' || **expStr == '%') {
        char op = **expStr;
        (*expStr)++;
        if (op == '*') {
            result *= number(expStr);
        } else if (op == '/') {
            result /= number(expStr);
        } else {
            result %= number(expStr);
        }
    }
    return result;
}
複製代碼

下一步就是把加減法裏的number解析都換成term解析,換完以後的expr函數會長這樣:

int expr(const char **expStr) {
    int result = term(expStr);
    while (**expStr == '+' || **expStr == '-') {
        char op = **expStr;
        (*expStr)++;
        if (op == '+') {
            result += term(expStr);
        } else {
            result -= term(expStr);
        }
    }
    return result;
}
複製代碼

實驗下,確認咱們的表達式解析器能夠按照正確的優先級計算加減乘除了。

加入括號的處理

括號的運算優先級是比乘除法還要高的,因此咱們新增一個非終結符factor(因子),用來在文法中描述它:

expr -> expr '+' term | expr '-' term | term
term -> term '*' factor | term '/' factor | factor
factor -> number | '(' expr ')'
number -> '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
複製代碼

嘿嘿,此次就真的不給你們貼代碼了,相信此次你們應該能夠熟練的搞定代碼,畢竟已是個成熟的程序猿了。

完整的代碼存放在SlimeExpressionC,須要的同窗能夠自取,今天的入門課就到這裏。

相關文章
相關標籤/搜索