【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 5.)(python/c/c++版)(笔记)Lexer词法分析程序
【編譯原理】讓我們來構建一個簡單的解釋器(Let’s Build A Simple Interpreter. Part 5.)
文章目錄
- python代碼
- C語言代碼
- 總結
你如何處理像理解如何創建解釋器或編譯器這樣復雜的事情?一開始,這一切看起來都像是一團亂七八糟的紗線,您需要將其解開才能獲得完美的球。
到達那里的方法是將它解開一根線,一次解開一個結。但是,有時您可能會覺得自己沒有立即理解某些內容,但您必須繼續前進。如果你足夠堅持,它最終會“咔噠”一聲,我向你保證(哎呀,如果我每次不明白某事時我就留出 25 美分,我很久以前就會變得富有了:)。
在理解如何創建解釋器和編譯器的過程中,我能給你的最好建議之一就是閱讀文章中的解釋,閱讀代碼,然后自己編寫代碼,甚至將相同的代碼編寫幾個一段時間后,使材料和代碼對您感覺自然,然后才能繼續學習新主題。不要著急,放慢腳步,花時間深入了解基本思想。這種方法雖然看似緩慢,但會在未來取得成效。相信我。
你最終會得到完美的毛線球。而且,你知道嗎?即使它不是那么完美,它仍然比替代方案更好,即什么都不做,不學習主題或快速瀏覽并在幾天內忘記它。
記住 - 繼續解開:一個線程,一次一個結,通過編寫代碼來練習你學到的東西,很多:
今天,您將使用從本系列前幾篇文章中獲得的所有知識,并學習如何解析和解釋具有任意數量的加法、減法、乘法和除法運算符的算術表達式。您將編寫一個解釋器,該解釋器將能夠計算諸如“14 + 2 * 3 - 6 / 2”之類的表達式。
在深入研究和編寫一些代碼之前,讓我們先談談運算符的結合性和優先級。
按照慣例,7 + 3 + 1 與 (7 + 3) + 1 相同,并且 7 - 3 - 1 相當于 (7 - 3) - 1。這里沒有意外。我們都在某個時候了解到這一點,并且從那時起就認為這是理所當然的。如果我們將 7 - 3 - 1 視為 7 - (3 - 1),結果將是意外的 5 而不是預期的 3。
在普通算術和大多數編程語言中,加法、減法、乘法和除法都是左結合的:
7 + 3 + 1 等價于 (7 + 3) + 1 7 - 3 - 1 等價于 (7 - 3) - 1 8 * 4 * 2 相當于 (8 * 4) * 2 8 / 4 / 2 等價于 (8 / 4) / 2運算符的左關聯意味著什么?
當表達式 7 + 3 + 1 中像 3 這樣的操作數兩邊都有加號時,我們需要一個約定來決定哪個運算符適用于 3。它是操作數 3 的左側還是右側?運算符 +與左側相關聯,因為兩邊都有加號的操作數屬于其左側的運算符,因此我們說運算符 + 是左結合的。這就是為什么 7 + 3 + 1 根據結合性 約定等價于 (7 + 3) + 1 。
好的,對于像 7 + 5 * 2 這樣的表達式,我們在操作數 5 的兩邊都有不同類型的運算符呢?表達式等價于 7 + (5 * 2) 還是 (7 + 5) * 2?我們如何解決這種歧義?
在這種情況下,結合性約定對我們沒有幫助,因為它僅適用于一種運算符,即加法 (+, -) 或乘法 (*, /)。當我們在同一表達式中有不同類型的運算符時,我們需要另一種約定來解決歧義。我們需要一個約定來定義運算符的相對優先級。
它是這樣的:我們說如果運算符 * 在 + 之前接受其操作數,那么它具有更高的優先級。在我們所知道和使用的算術中,乘法和除法的優先級高于加法和減法。因此,表達式 7 + 5 * 2 等價于 7 + (5 * 2),而表達式 7 - 8 / 4 等價于 7 - (8 / 4)。
在我們有一個具有相同優先級的運算符的表達式的情況下,我們只使用結合性約定并從左到右執行運算符:
7 + 3 - 1 等價于 (7 + 3) - 1 8 / 4 * 2 相當于 (8 / 4) * 2我希望你不會認為我想通過談論運算符的結合性和優先級來讓你厭煩。這些約定的好處是我們可以從一個表中構造算術表達式的語法,該表顯示算術運算符的結合性和優先級。然后,我們可以按照我在第 4 部分中概述的準則將語法翻譯成代碼,我們的解釋器將能夠處理除結合性之外的運算符的優先級。
好的,這是我們的優先級表:
從表中,您可以看出運算符 + 和 - 具有相同的優先級,并且它們都是左關聯的。您還可以看到運算符 * 和 / 也是左結合的,它們之間具有相同的優先級,但比加法和減法運算符具有更高的優先級。
以下是如何從優先級表構造文法的規則:
1、為每個優先級定義一個非終結符。非終結符的產生式主體應包含該級別的算術運算符和下一個更高優先級的非終結符。(啥意思??)
2、為基本的表達單位創建一個額外的非終結因子,在我們的例子中是整數。一般規則是,如果您有 N 個優先級,則總共需要 N + 1 個非終結符:每個級別一個非終結符加上一個基本表達式單位的非終結符。
向前!
讓我們遵循規則并構建我們的文法。
根據規則1,我們將定義兩個非端子:非末端稱為EXPR 2級和非末端稱為術語為1級,并按照規則2我們將定義一個因子非終端的運算單元的基本表達式,整數。
我們新語法的開始符號將是expr并且expr產生式將包含一個主體,表示使用第 2 級的運算符,在我們的例子中是運算符 + 和 - ,并將包含用于下一個更高級別的術語非終結符優先級,級別 1:
該術語生產將有一個代表從1級,這是操作員使用操作員的身體*和/,并將包含非末端因子表達,整數的基本單位:
非終端因子的產生將是:
您已經在前面的文章中將上述產生式視為語法和語法圖的一部分,但在這里我們將它們組合成一個語法來處理結合性和運算符的優先級:
這是對應于上述語法的語法圖:
圖中的每個矩形框都是對另一個圖的“方法調用”。如果您采用表達式 7 + 5 * 2 并從最上面的圖表expr開始,一直走到最底部的圖表factor,您應該能夠看到下圖中的高優先級運算符 * 和 / 在運算符 + 之前執行和 - 在更高的圖表中。
為了推動運算符的優先級,讓我們看一下根據上面的語法和語法圖完成的相同算術表達式 7 + 5 * 2 的分解。這只是表明優先級較高的運算符在優先級較低的運算符之前執行的另一種方式:
好的,讓我們按照第 4 部分的指南將語法轉換為代碼,看看我們的新解釋器是如何工作的,好嗎?
這里又是語法:
這是一個計算器的完整代碼,它可以處理包含整數和任意數量的加法、減法、乘法和除法運算符的有效算術表達式。
以下是與第 4 部分中的代碼相比的主要變化:
- 該詞法分析器類現在可以記號化+, - ,*,和/(這里沒有什么新,我們只是結合的編碼從之前的文章中為一類,它支持所有的標記)
- 回想一下,在語法中定義的每個規則(產生式)R變成了一個同名的方法,并且對該規則的引用變成了一個方法調用:R()。因此,Interpreter類現在具有三個對應于語法中非終結符的方法:expr、term和factor。
python代碼
# -*- coding: utf-8 -*- """ @File : calc5.py @Time : 2021/7/19 21:23 @Author : Dontla @Email : sxana@qq.com @Software: PyCharm """ # Token types # # EOF (end-of-file) token is used to indicate that # there is no more input left for lexical analysis INTEGER, PLUS, MINUS, MUL, DIV, EOF = ('INTEGER', 'PLUS', 'MINUS', 'MUL', 'DIV', 'EOF' )class Token(object):def __init__(self, type, value):# token type: INTEGER, PLUS, MINUS, MUL, DIV, or EOFself.type = type# token value: non-negative integer value, '+', '-', '*', '/', or Noneself.value = valuedef __str__(self):"""String representation of the class instance.Examples:Token(INTEGER, 3)Token(PLUS, '+')Token(MUL, '*')"""return 'Token({type}, {value})'.format(type=self.type,value=repr(self.value))def __repr__(self):return self.__str__()class Lexer(object):def __init__(self, text):# client string input, e.g. "3 * 5", "12 / 3 * 4", etcself.text = text# self.pos is an index into self.textself.pos = 0self.current_char = self.text[self.pos]def error(self):raise Exception('Invalid character')def advance(self):"""Advance the `pos` pointer and set the `current_char` variable."""self.pos += 1if self.pos > len(self.text) - 1:self.current_char = None # Indicates end of inputelse:self.current_char = self.text[self.pos]def skip_whitespace(self):while self.current_char is not None and self.current_char.isspace():self.advance()def integer(self):"""Return a (multidigit) integer consumed from the input."""result = ''while self.current_char is not None and self.current_char.isdigit():result += self.current_charself.advance()return int(result)def get_next_token(self):"""Lexical analyzer (also known as scanner or tokenizer)This method is responsible for breaking a sentenceapart into tokens. One token at a time."""while self.current_char is not None:if self.current_char.isspace():self.skip_whitespace()continueif self.current_char.isdigit():return Token(INTEGER, self.integer())if self.current_char == '+':self.advance()return Token(PLUS, '+')if self.current_char == '-':self.advance()return Token(MINUS, '-')if self.current_char == '*':self.advance()return Token(MUL, '*')if self.current_char == '/':self.advance()return Token(DIV, '/')self.error()return Token(EOF, None)class Interpreter(object):def __init__(self, lexer):self.lexer = lexer# set current token to the first token taken from the inputself.current_token = self.lexer.get_next_token()def error(self):raise Exception('Invalid syntax')def eat(self, token_type):# compare the current token type with the passed token# type and if they match then "eat" the current token# and assign the next token to the self.current_token,# otherwise raise an exception.if self.current_token.type == token_type:self.current_token = self.lexer.get_next_token()else:self.error()def factor(self):"""factor : INTEGER"""token = self.current_tokenself.eat(INTEGER)return token.valuedef term(self):"""term : factor ((MUL | DIV) factor)*"""result = self.factor()while self.current_token.type in (MUL, DIV):token = self.current_tokenif token.type == MUL:self.eat(MUL)result = result * self.factor()elif token.type == DIV:self.eat(DIV)result = result / self.factor()return resultdef expr(self):"""Arithmetic expression parser / interpreter.calc> 14 + 2 * 3 - 6 / 217expr : term ((PLUS | MINUS) term)*term : factor ((MUL | DIV) factor)*factor : INTEGER"""result = self.term()while self.current_token.type in (PLUS, MINUS):token = self.current_tokenif token.type == PLUS:self.eat(PLUS)result = result + self.term()elif token.type == MINUS:self.eat(MINUS)result = result - self.term()return resultdef main():while True:try:# To run under Python3 replace 'raw_input' call# with 'input'# text = raw_input('calc> ')text = input('calc> ')except EOFError:breakif not text:continuelexer = Lexer(text)interpreter = Interpreter(lexer)result = interpreter.expr()print(result)if __name__ == '__main__':main()運行結果:
D:\python_virtualenv\my_flask\Scripts\python.exe C:/Users/Administrator/Desktop/編譯原理/python/calc5.py calc> 3+ 3 * 1 - 4 /2 +1 5.0 calc>C語言代碼
#include <stdio.h> #include <stdlib.h> #include <memory.h> #include <string.h> #include<math.h>#define flag_digital 0 #define flag_plus 1 #define flag_minus 2 #define flag_multiply 3 #define flag_divide 4#define flag_EOF 5struct Token {int type;int value; };struct Lexer {char* text;int pos; };struct Interpreter {struct Lexer* lexer;struct Token current_token; };void error() {printf("輸入非法!\n");exit(-1); }void skip_whitespace(struct Lexer* le) {while (le->text[le->pos] == ' ') {le->pos++;} }//判斷Interpreter中當前pos是不是數字 int is_integer(char c) {if (c >= '0' && c <= '9')return 1;elsereturn 0; }void advance(struct Lexer* le) {le->pos++; }char current_char(struct Lexer* le) {return(le->text[le->pos]); }//獲取數字token的數值(把數字字符數組轉換為數字) int integer(struct Lexer* le) {char temp[20];int i = 0;while (is_integer(le->text[le->pos])) {temp[i] = le->text[le->pos];i++;advance(le);}int result = 0;int j = 0;int len = i;while (j < len) {result += (temp[j] - '0') * pow(10, len - j - 1);j++;}return result; }void get_next_token(struct Interpreter* pipt) {//先跳空格,再判斷有沒有結束符if (current_char(pipt->lexer) == ' ')skip_whitespace(pipt->lexer);if (pipt->lexer->pos > (strlen(pipt->lexer->text) - 1)) {pipt->current_token = { flag_EOF, NULL };return;}char current = current_char(pipt->lexer);if (is_integer(current)) {pipt->current_token = { flag_digital, integer(pipt->lexer)};return;}if (current == '+') {pipt->current_token = { flag_plus, NULL };pipt->lexer->pos++;return;}if (current == '-') {pipt->current_token = { flag_minus, NULL };pipt->lexer->pos++;return;}if (current == '*') {pipt->current_token = { flag_multiply, NULL };pipt->lexer->pos++;return;}if (current == '/') {pipt->current_token = { flag_divide, NULL };pipt->lexer->pos++;return;}error();//如果都不是以上的字符,則報錯并退出程序 }int eat(struct Interpreter* pipt, int type) {int current_token_value = pipt->current_token.value;if (pipt->current_token.type == type) {get_next_token(pipt);return current_token_value;}else {error();} }int factor(struct Interpreter* pipt) {return eat(pipt, flag_digital); }//判斷乘除 int term(struct Interpreter* pipt) {int result = factor(pipt);while (true) {int token_type = pipt->current_token.type;if (token_type == flag_multiply) {eat(pipt, flag_multiply);result = result * factor(pipt);}else if (token_type == flag_divide) {eat(pipt, flag_divide);result = result / factor(pipt);}else {return result;}} } int expr(struct Interpreter* pipt) {get_next_token(pipt);int result = term(pipt);while (true) {int token_type = pipt->current_token.type;if (token_type == flag_plus) {eat(pipt, flag_plus);result = result + term(pipt);}else if (token_type == flag_minus) {eat(pipt, flag_minus);result = result - term(pipt);}else {return result;}} }int main() {char text[50];while (1){printf("請輸入算式:\n");//scanf_s("%s", text, sizeof(text));//sanf沒法輸入空格?int i = 0;while ((text[i] = getchar()) != '\n') {//putchar(text[i]);i++;}text[i] = '\0';struct Lexer le = {text, 0};struct Interpreter ipt = { &le };int result = expr(&ipt);printf("= %d\n\n", result);}return 0; }運行結果:
請輸入算式: 3 +4/2*4 +2/1 = 13雖然跑成功了,但還是有點懵逼,條例未理得很清楚。。。
總結
-
按照本文中的描述編寫一個解釋器,無需查看文章中的代碼。為您的解釋器編寫一些測試,并確保它們通過。
-
擴展解釋器以處理包含括號的算術表達式,以便您的解釋器可以評估深度嵌套的算術表達式,例如:7 + 3 * (10 / (12 / (3 + 1) - 1))
檢查你的理解。
- 運算符的左關聯意味著什么?
- 運算符 + 和 - 是 左關聯還是右關聯?* 和 / 呢?
- 運算符 + 是否比運算符 *具有更高的優先級?
expr(處理加減) -> term(處理乘除) -> factor(處理數字)
總結
以上是生活随笔為你收集整理的【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 5.)(python/c/c++版)(笔记)Lexer词法分析程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【编译原理】让我们来构建一个简单的解释器
- 下一篇: 【编译原理】让我们来构建一个简单的解释器