從0開始自制解釋器——實現(xiàn)簡單的加法計算器

為什么要學習編譯器和解釋器呢?文中的作者給出的答案有下面幾個:

  1. 為了深入理解計算機是如何工作的:一個顯而易見的道理就是,如果你不懂編譯器和解釋器是如何工作的那么你就不明白計算機是如何工作的
  2. 編譯器和解釋器用到的一些原理和編程技巧以及算法在其他地方也可以用到。學習編譯器和解釋器能夠學到并強化這些技巧的運用
  3. 為了方便日后能編寫自己的編程語言或者專用領域的特殊語言

接下來我們就從0開始一步一步的構建自己的解釋器。跟著教程先制作一個簡單的加法計算器,為了保證簡單,這個加法計算器能夠解析的表達式需要滿足下面幾點:

  1. 目前只支持加法運算
  2. 目前只支持兩個10以內的整數(shù)的計算
  3. 表達式之間不能有空格
  4. 只能計算一次加法

舉一個例子來說,它可以計算諸如"1+2"、"5+6" 這樣的表達式,但是不能計算像 "11+20"(必須是10以內)、"1.1+2"(需要兩個數(shù)都是整數(shù))、"1 + 2"(中間不能有空格)、"1+2+3"(只能計算一次加法)

有了這些限制,我們很容易就能實現(xiàn)出來。

實現(xiàn)的算法

假設我們要計算表達式 5+6。這里主要的步驟是通過字符串保存表達式,然后通過索引依次訪問每個字符,分別找到兩個整數(shù)和加法運算符,最后實現(xiàn)兩個整數(shù)相加的操作。

第一步,我們的索引在表達式字符串的開始位置,解析得到當前位置的字符是一個整數(shù),我們給它打上標記,類型為整形,值為5。


1.png

第二步,索引向前推進,解析當前位置的字符是一個+。還是給它打上標記,類型為plus,值為+。

2.png

第三步,索引繼續(xù)前進,解析到當前位置的字符是一個整數(shù),我們給它打上標記,類型為整形,值為6


3.png

最后一步,根據(jù)得到的兩個整數(shù)以及要執(zhí)行的算術運算,我們將兩個數(shù)直接進行相加得到最終結果

具體的代碼

首先我們定義這個標記的類型,目前支持整數(shù)以及加法的標記

typedef enum e_TokenType
{
    CINT = 0, //整型
    PLUS //加法運算符
}ETokenType;

// 這里因為只支持10以內的整數(shù),所以表示計算數(shù)字的字符只有一個,加上字符串最后的結束標記,字符數(shù)組只需要兩個即可
typedef struct Token
{
    ETokenType type; //類型
    char value[2]; //值
}Token, *LPTOKEN;

接著定義一些全局變量來保存算術運算的表達式和當前指針的索引

char* g_pszUserBuf = NULL;
char* g_pPosition = NULL;

接著我們定義一個函數(shù)來模擬上述說到的不斷解析每一個字符的過程

bool get_next_token(LPTOKEN pToken)
{
    char* sz = g_pPosition;
    g_pPosition++;
    pToken->value[0] = '\0';
    if (*sz >= '0' && *sz <= '9')
    {
        pToken->type = CINT;
        pToken->value[0] = *sz;
        return true;
    }
    else if (*sz == '+')
    {
        pToken->type = PLUS;
        pToken->value[0] = *sz;
        return true;
    }
    else
    {
        pToken->value[0] = '\0';
        return false;
    }
}

最后我們定義一個函數(shù)來執(zhí)行獲取每個標記并最終計算結果的操作

int expr()
{
    int val1 = 0, val2 = 0;
    Token token = { 0 };
    if (get_next_token(&token) && token.type == CINT)
    {
        val1 = atoi(token.value);
    }
    else
    {
        printf("首個字符必須是整數(shù)");
        return -1;
    }

    if (get_next_token(&token) && token.type == PLUS)
    {
    }
    else
    {
        printf("第二個字符必須是操作符,并且當前只支持 + 運算");
        return -1;
    }

    if (get_next_token(&token) && token.type == CINT)
    {
        val2 = atoi(token.value);
    }

    printf("%d+%d=%d\n", val1, val2, val1 + val2);
}

main函數(shù)里面我們只需要建立一個緩沖來保存字符,并且在循環(huán)中不斷等待用戶輸入,完成解析并輸出結果即可

// 重制當前解析環(huán)境
void reset()
{
    memset(g_pszUserBuf, 0x00, 16 * sizeof(char));
    scanf_s("%s", g_pszUserBuf);
    g_pPosition = g_pszUserBuf;
}

int main()
{
    g_pszUserBuf = (char*)malloc(16 * sizeof(char));
    while (1)
    {
        printf(">>>");
        reset();
        if (strcmp(g_pszUserBuf, "exit") == 0)
        {
            break;
        }
        expr();
    }
    return 0;
}

最終執(zhí)行的結果如下


4.png

最后的總結

程序我們已經寫完了,你可能覺得這個程序太簡單了,只能做這點事情。別著急,后面將會逐步的去完善這個程序。以便它能實現(xiàn)更加復雜的運算。

最后我們來引入一些概念性的東西:

  1. 我們將輸入內容按照一定規(guī)則打上的標記被稱之為Token
  2. 上述get_next_token函數(shù)體現(xiàn)的將一段字符串分割并打上有意義的標簽的過程被稱為詞法分析。
  3. 解釋器工作的第一步就是將輸入的字符串按照一定的規(guī)則轉換為一系列有意義的標記。完成這個工作的組件被稱之為詞法分析器,也可以被稱為掃描器或者分詞器
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容