从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阅读 219,188评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,464评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,562评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,893评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,917评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,708评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,430评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,342评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,801评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,976评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,115评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,804评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,458评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,008评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,135评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,365评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,055评论 2 355

推荐阅读更多精彩内容