【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 3.)(python/c/c++版)(笔记)
【編譯原理】讓我們來構建一個簡單的解釋器(Let’s Build A Simple Interpreter. Part 3.)
文章目錄
- python代碼calc3.py
- C語言代碼(calc3.cpp)(有bug,最后面數字后不能有空格,否則報錯)
- C語言代碼(修復版,跳空格放在判斷結束符前執行,所以用戶輸入最后面可包含空格)
- 總結
到目前為止,您已經學習了如何解釋對兩個整數進行加減運算的算術表達式,例如“7 + 3”或“12 - 9”。今天我將討論如何解析(識別)和解釋包含任意數量的加號或減號運算符的算術表達式,例如“7 - 3 + 2 - 1”。
從圖形上看,本文中的算術表達式可以用以下語法圖表示:
什么是語法圖(syntax diagram)?一個語法圖是一種編程語言的語法規則的圖示?;旧?#xff0c;語法圖直觀地向您展示了您的編程語言中允許哪些語句,哪些不允許。
語法圖很容易閱讀:只需按照箭頭指示的路徑進行操作即可。一些路徑表示選擇(choices)。并且一些路徑表示循環(loops)。
您可以閱讀上面的語法圖如下:一個術語(term)可選地后跟一個加號或減號,然后是另一個術語,而另一個術語又可選地后跟一個加號或減號,然后是另一個術語,依此類推。你得到了圖片,字面意思。您可能想知道什么是“術語”。就本文而言,“術語”只是一個整數。
語法圖有兩個主要目的:
- 它們以圖形方式表示編程語言的規范(語法)。
- 它們可用于幫助您編寫解析器——您可以按照簡單的規則將圖表映射到代碼。
您已經了解到在標記流中識別短語的過程稱為解析。執行該工作的解釋器或編譯器的部分稱為解析器。解析也稱為語法分析,解析器也被恰當地稱為,你猜對了,語法分析器。
根據上面的語法圖,以下所有算術表達式都是有效的:
3 3 + 4 7 - 3 + 2 - 1由于不同編程語言中算術表達式的語法規則非常相似,我們可以使用 Python shell 來“測試”我們的語法圖。啟動你的 Python shell 并親眼看看:
>>> 3 3 >>> 3 + 4 7 >>> 7 - 3 + 2 - 1 5這里沒有驚喜。
表達式“3 + ”不是有效的算術表達式,因為根據語法圖,加號后面必須跟一個術語(整數),否則就是語法錯誤。再一次,用 Python shell 嘗試一下,親眼看看:
>>> 3 +文件"<stdin>",第1行3 +^ 語法錯誤:無效語法能夠使用 Python shell 進行一些測試是很棒的,但是讓我們將上面的語法圖映射到代碼并使用我們自己的解釋器進行測試,好嗎?
您從之前的文章(第 1 部分和第 2 部分)中知道expr方法是我們的??解析器和解釋器所在的地方。同樣,解析器只是識別結構以確保它符合某些規范,并且一旦解析器成功識別(解析)它,解釋器就會實際評估表達式。
以下代碼片段顯示了與圖表對應的解析器代碼。語法圖(term)中的矩形框變成了解析整數的term方法,而expr方法只是遵循語法圖流程:
def term(self):self.eat(INTEGER)def expr(self):# set current token to the first token taken from the inputself.current_token = self.get_next_token()self.term()while self.current_token.type in (PLUS, MINUS):token = self.current_tokenif token.type == PLUS:self.eat(PLUS)self.term()elif token.type == MINUS:self.eat(MINUS)self.term()可以看到expr首先調用了term方法。然后expr方法有一個可以執行零次或多次的while循環。在循環內部,解析器根據標記(無論是加號還是減號)做出選擇?;ㄒ恍r間向自己證明上面的代碼確實遵循算術表達式的語法圖流程。
解析器本身并不解釋任何東西:如果它識別出一個表達式,它就會保持沉默,如果沒有,它會拋出一個語法錯誤。讓我們修改expr方法并添加解釋器代碼:
def term(self):"""Return an INTEGER token value"""token = self.current_tokenself.eat(INTEGER)return token.valuedef expr(self):"""Parser / Interpreter """# set current token to the first token taken from the inputself.current_token = self.get_next_token()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 result由于解釋器需要對表達式求值,因此修改term方法以返回整數值,修改expr方法以在適當的位置執行加減運算并返回解釋結果。盡管代碼非常簡單,但我還是建議花一些時間研究它。
現在開始動起來,看看解釋器的完整代碼,好嗎?
這是新版本計算器的源代碼,它可以處理包含整數和任意數量的加減運算符的有效算術表達式:
python代碼calc3.py
# Token types # # EOF (end-of-file) token is used to indicate that # there is no more input left for lexical analysis INTEGER, PLUS, MINUS, EOF = 'INTEGER', 'PLUS', 'MINUS', 'EOF'class Token(object):def __init__(self, type, value):# token type: INTEGER, PLUS, MINUS, 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, '+')"""return 'Token({type}, {value})'.format(type=self.type,value=repr(self.value))def __repr__(self):return self.__str__()class Interpreter(object):def __init__(self, text):# client string input, e.g. "3 + 5", "12 - 5 + 3", etcself.text = text# self.pos is an index into self.textself.pos = 0# current token instanceself.current_token = Noneself.current_char = self.text[self.pos]########################################################### Lexer code ###########################################################def error(self):raise Exception('Invalid syntax')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, '-')self.error()return Token(EOF, None)########################################################### Parser / Interpreter code ###########################################################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.get_next_token()else:self.error()def term(self):"""Return an INTEGER token value."""token = self.current_tokenself.eat(INTEGER)return token.valuedef expr(self):"""Arithmetic expression parser / interpreter."""# set current token to the first token taken from the inputself.current_token = self.get_next_token()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> ')except EOFError:breakif not text:continueinterpreter = Interpreter(text)result = interpreter.expr()print(result)if __name__ == '__main__':main()運行結果:
D:\python_virtualenv\my_flask\Scripts\python.exe C:/Users/Administrator/Desktop/編譯原理/python/calc3.py calc> 3 + 4 7 calc> 3-4 -1 calc> 3+ 3 -5 +4 5C語言代碼(calc3.cpp)(有bug,最后面數字后不能有空格,否則報錯)
#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_EOF 3struct Token {int type;int value; };struct Interpreter {char* text;int pos;struct Token current_token; };void error() {printf("輸入非法!\n");exit(-1); }void skip_whitespace(struct Interpreter* pipt) {while (pipt->text[pipt->pos] == ' ') {pipt->pos++;} }//判斷Interpreter中當前pos是不是數字 int is_integer(char c) {if (c >= '0' && c <= '9')return 1;elsereturn 0; }void advance(struct Interpreter* pipt) {pipt->pos++; }char current_char(struct Interpreter* pipt) {return(pipt->text[pipt->pos]); }//獲取數字token的數值(把數字字符數組轉換為數字) int integer(struct Interpreter* pipt) {char temp[20];int i = 0;while (is_integer(pipt->text[pipt->pos])) {temp[i] = pipt->text[pipt->pos];i++;advance(pipt);}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 (pipt->pos > (strlen(pipt->text) - 1)) {pipt->current_token = { flag_EOF, NULL };return;}if (current_char(pipt) == ' ')skip_whitespace(pipt);if (is_integer(current_char(pipt))) {pipt->current_token = { flag_digital, integer(pipt) };return;}if (current_char(pipt) == '+') {pipt->current_token = { flag_plus, NULL };pipt->pos++;return;}if (current_char(pipt) == '-') {pipt->current_token = { flag_minus, NULL };pipt->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 term(struct Interpreter* pipt) {return eat(pipt, flag_digital); }int expr(char* text) {struct Interpreter ipt = { text, 0 };get_next_token(&ipt);int result;result = term(&ipt);while (true) {int token_type = ipt.current_token.type;if (token_type == flag_plus) {eat(&ipt, flag_plus);result = result + term(&ipt);}else if (token_type == flag_minus) {eat(&ipt, flag_minus);result = result - term(&ipt);}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';int result = expr(text);printf("= %d\n\n", result);}return 0; }運行結果:(最后面有空格會報錯!)
請輸入算式: 4+4 = 8請輸入算式: 3-4 = -1請輸入算式:3+4 -4 = 3請輸入算式: 4 + 4 - 4 = 4請輸入算式: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_EOF 3struct Token {int type;int value; };struct Interpreter {char* text;int pos;struct Token current_token; };void error() {printf("輸入非法!\n");exit(-1); }void skip_whitespace(struct Interpreter* pipt) {while (pipt->text[pipt->pos] == ' ') {pipt->pos++;} }//判斷Interpreter中當前pos是不是數字 int is_integer(char c) {if (c >= '0' && c <= '9')return 1;elsereturn 0; }void advance(struct Interpreter* pipt) {pipt->pos++; }char current_char(struct Interpreter* pipt) {return(pipt->text[pipt->pos]); }//獲取數字token的數值(把數字字符數組轉換為數字) int integer(struct Interpreter* pipt) {char temp[20];int i = 0;while (is_integer(pipt->text[pipt->pos])) {temp[i] = pipt->text[pipt->pos];i++;advance(pipt);}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) == ' ')skip_whitespace(pipt);if (pipt->pos > (strlen(pipt->text) - 1)) {pipt->current_token = { flag_EOF, NULL };return;}char current = current_char(pipt);if (is_integer(current)) {pipt->current_token = { flag_digital, integer(pipt) };return;}if (current == '+') {pipt->current_token = { flag_plus, NULL };pipt->pos++;return;}if (current == '-') {pipt->current_token = { flag_minus, NULL };pipt->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 term(struct Interpreter* pipt) {return eat(pipt, flag_digital); }int expr(char* text) {struct Interpreter ipt = { text, 0 };get_next_token(&ipt);int result;result = term(&ipt);while (true) {int token_type = ipt.current_token.type;if (token_type == flag_plus) {eat(&ipt, flag_plus);result = result + term(&ipt);}else if (token_type == flag_minus) {eat(&ipt, flag_minus);result = result - term(&ipt);}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';int result = expr(text);printf("= %d\n\n", result);}return 0; }執行結果:(輸入你看不出后面有空格,但其實有空格)
請輸入算式: 3+5 = 8請輸入算式: 3-1 = 2請輸入算式: 3 +4 = 7請輸入算式: 3+4-3 +3 = 7請輸入算式:4-4 +4 = 4請輸入算式:總結
記住我在文章開頭提到的那些練習:它們在這里,正如承諾的那樣:)
為僅包含乘法和除法的算術表達式繪制語法圖,例如“7 * 4 / 2 * 3”。說真的,只要拿起一支鋼筆或一支鉛筆,試著畫一個。
修改計算器的源代碼以解釋僅包含乘法和除法的算術表達式,例如“7 * 4 / 2 * 3”。
編寫一個解釋器,從頭開始處理像“7 - 3 + 2 - 1”這樣的算術表達式。使用您熟悉的任何編程語言,并在不查看示例的情況下將其寫下來。當你這樣做,想想涉及的組件:一個詞法分析器,其采用輸入,并將其轉換成標記流,parser是投喂所提供的標記流飼料詞法分析器,并試圖在該流識別的結構,在解析器之后生成結果的解釋器已成功解析(識別)一個有效的算術表達式。將這些碎片串在一起?;ㄒ恍r間將您獲得的知識翻譯成算術表達式的工作解釋器。
檢查你的理解。
什么是語法圖?
什么是語法分析?
什么是語法分析器?
總結
以上是生活随笔為你收集整理的【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 3.)(python/c/c++版)(笔记)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【编译原理】让我们来构建一个简单的解释器
- 下一篇: 【编译原理】让我们来构建一个简单的解释器