從0開始自制解釋器——添加對括號的支持

在上一篇我們添加了對乘除法的支持,也介紹了BNF范式,并且針對當前的算術表達式寫出了對應的范式,同時根據范式給出相應的代碼實現。這篇我們將繼續為算數表達式添加對括號的支持。

對應的BNF 范式

在上一篇我們給出了乘除法對應的范式

<expr>::=<term>{(PLUS|MINUS)<term>}
<term>::=<factor>{(DIV|MUL)<factor>}
<factor>::={(0|1|2|3|4|5|6|7|8|9)}

針對乘除法的優先級比加減法高,我們的做法是將乘除法單獨作為一個部分,然后在最外層表達式中只處理加減法。基于這種思路,我們來看如何處理括號的問題。例如下面的算數表達式

((1+2)*3+4) - (5 - 6 / 3)

這里我們直接給出對應的文法,然后再來分析一下該如何由這個文法得到對應的表達式

<expr>::=<term>{(PLUS|MINUS)<term>}
<term>::=<factor>{(DIV|MUL)<factor>}
<factor>::=({(0|1|2|3|4|5|6|7|8|9|)})|LPAREN<expr>RPAREN
  1. 首先根據表達式,它應該由兩個term來組成 expr = term - term
  2. 接著看看兩個term,它們并不是單純的加法運算,所以兩個term應該只有單純的一個factor,也就是 expr = factor - factor
  3. 因為最外層都有括號,所以再次展開 expr = (expr1) - (expr2)
  4. 這時就又到了分析expr的過程了,左側的expr最外層是一個加法,所以這里可以得到 expr1 = term + term
  5. 右側的expr 最外層是一個減法,也就是 expr2 = term - term
  6. 結合最外層的表達式可以得到 expr = (term1 + term2) - (term3 - term4)
  7. term1 部分有一個乘法,所以它可以解析為 term1 = factor * factor
  8. term2 部分就是單獨的數字所以可以得到 term2 = factor,并且進一步得到 term2=4
  9. term3 部分就是單純的數字,可以得到 term3 = factor,并且進一步得到 term3=5
  10. term4 部分有一個除法,所以它可以解析為 term3 = factor / factor
  11. 此時整個表達式可以表示為 expr = (factor1 * factor2 + 4) - (5 - factor3 / factor4)
  12. factor1 本身也是一個括號,加表達式,所以它可以表示為 factor1 = (expr)
  13. factor2 是一個數字,所以它表示為 factor2 = 3
  14. factor3 是一個數字,所以它表示為 factor3 = 6
  15. factor4 是一個數字,所以它表示為 factor4 = 3
  16. 此時表達式可以是 expr = ((expr1) * 3 + 4) - (5 - 6 / 3)
  17. 此時再次分析這個 expr1 可以得到 expr1 = 1+2
  18. 這個時候,整個表達式就出來了 expr = ((1+2) * 3 + 4) - (5 - 6 / 3)

用圖來表示大概可以表示如下

代碼實現

有了范式,我們就可以按照范式來組織代碼實現。

首先我們先在 ETokenType 中添加針對括號的標簽

typedef enum e_TokenType
{
    CINT = 0, //整數
    PLUS, //加法
    MINUS, //減法
    DIV, //乘法
    MUL, //除法
    LPAREN, //左括號
    RPAREN, //右括號
    END_OF_FILE // 字符串末尾結束符號
}ETokenType;

然后在 get_next_token 函數中添加對括號進行詞法分析并打標簽的功能

bool get_next_token(LPTOKEN pToken)
{
    char c = get_next_char();

    dyncstring_reset(&pToken->value);
    if (is_digit(c))
    {
        dyncstring_catch(&pToken->value, c);
        pToken->type = CINT;
        parser_number(&pToken->value);
    }
    else if(is_space(c))
    {
        skip_whitespace();
        return get_next_token(pToken);
    }
    else
    {
        switch (c) {
        case '+':
            pToken->type = PLUS;
            break;
        case '-':
            pToken->type = MINUS;
            break;
        case '*':
            pToken->type = DIV;
            break;
        case '/':
            pToken->type = MUL;
            break;
        case '(':
            pToken->type = LPAREN;
            break;
        case ')':
            pToken->type = RPAREN;
            break;
        case '\0':
            pToken->type = END_OF_FILE;
            break;
        default:
            return false;
        }
    }

    return true;
}

這里我對這個函數進行了一些改寫,針對依靠單個字符就能打上標簽的采用switc來進行處理,像空白字符、數字這種有多種字符類型的就采用普通的if處理。

然后在get_oper 中添加對括號的識別

    if (get_next_token(&token) && (token.type == PLUS || token.type == MINUS || token.type == DIV || token.type == MUL || token.type == LPAREN || token.type == RPAREN))
    {
        oper = token.type;
        if (pRet)
            *pRet = true;
    }

然后根據文法,get_factor 需要能夠返回一個 expr的結果,所以這里需要添加以下代碼

    if (token.type == LPAREN)
    {
        bool bValid = true;
        value = expr(&bValid);
        if (!bValid)
            *pRet = false;

        if (get_next_token(&token) && token.type == RPAREN)
            *pRet = true;
        else
            *pRet = false;
    }

如果我們得到的標簽不為括號則按照原來的處理方式來處理,如果是括號,則將括號中的內容作為表達式并計算表達式的值,作為整數來返回。之前的expr 函數我們僅僅將結果打印并返回是否解析成功,這里需要做一些改進。我們使用一個傳出參數來返回解析是否成功,而將計算結果作為值進行返回。

另外需要特別注意的是,我們將反括號的判斷放到了 get_factor 函數中,所以在 get_termexpr 中,遇到反括號應該考慮對位置索引進行遞減,并且遇到反括號應該認為到達末尾并推出。這里的代碼就不貼出來了。有興趣的小伙伴可以看github上上傳的代碼。地址

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容