【编译原理】构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 8.)(笔记)一元运算符正负(+,-)
【編譯原理】讓我們來構建一個簡單的解釋器(Let’s Build A Simple Interpreter. Part 8.)
文章目錄
- C語言代碼(作者沒提供完整的python代碼,關鍵的改動提供了,在上面,完整的python代碼這里略掉)
今天我們將討論一元運算符,即一元加(+)和一元減(-)運算符。
今天的很多資料都基于上一篇文章中的資料,因此如果您需要復習,請返回第 7 部分并再次閱讀。請記住:重復是所有學習之母。
話雖如此,這就是你今天要做的:
- 擴展語法以處理一元加號和一元減號運算符
- 添加一個新的UnaryOp AST節點類
- 擴展解析器以生成具有UnaryOp 節點的AST
- 擴展解釋器并添加一個新的visit_UnaryOp方法來解釋一元運算符
讓我們開始吧,好嗎?
到目前為止,我們只使用了二元運算符(+、-、*、/),即對兩個操作數進行運算的運算符。
那什么是一元運算符呢?一元運算符是 僅對一個操作數進行運算的運算符。
以下是一元加號和一元減號運算符的規則:
- 一元減號 (-) 運算符產生其數值操作數的否定
- 一元加號 (+) 運算符生成其數字操作數而無需更改
- 一元運算符的優先級高于二元運算符 +、-、* 和 /
在表達式“+ - 3”中,第一個’+‘運算符代表一元加運算,第二個’-'運算符代表一元減運算。表達式“+ - 3”等價于“+ (- (3))”,它等于-3。也可以說表達式中的-3是一個負整數,但在我們的例子中,我們將其視為一元減運算符,其中 3 作為其正整數操作數:
我們再來看看另一個表達式,“5 - - 2”:
在表達式“5 - - 2”中,第一個’-‘代表二元減法運算,第二個’-'代表一元減法運算,即否定。
還有一些例子:
現在讓我們更新我們的語法以包含一元加號和一元減號運算符。我們將修改因子規則并在那里添加一元運算符,因為一元運算符的優先級高于二元 +、-、* 和 / 運算符。
這是我們當前的因子 規則:
這是我們處理一元加和一元減運算符的更新因子規則:
如您所見,我將因子規則擴展為引用自身,這使我們能夠推導出諸如“- - - + - 3”之類的表達式,這是一個帶有許多一元運算符的合法表達式。
這是現在可以使用一元加號和一元減號運算符派生表達式的完整語法:
下一步是添加一個AST節點類來表示一元運算符。
這個會做:
class UnaryOp(AST):def __init__(self, op, expr):self.token = self.op = opself.expr = expr構造函數接受兩個參數:op,它代表一元運算符標記(加號或減號)和expr,它代表一個AST 節點。
我們更新的語法對factor規則進行了更改,因此這就是我們要在解析器中修改的內容 -factor方法。我們將添加代碼來處理方法“(PLUS | MINUS)factor”分治:
def factor(self):"""factor : (PLUS | MINUS) factor | INTEGER | LPAREN expr RPAREN"""token = self.current_tokenif token.type == PLUS:self.eat(PLUS)node = UnaryOp(token, self.factor())return nodeelif token.type == MINUS:self.eat(MINUS)node = UnaryOp(token, self.factor())return nodeelif token.type == INTEGER:self.eat(INTEGER)return Num(token)elif token.type == LPAREN:self.eat(LPAREN)node = self.expr()self.eat(RPAREN)return node現在我們需要擴展Interpreter類并添加一個visit_UnaryOp方法來解釋一元節點:
def visit_UnaryOp(self, node):op = node.op.typeif op == PLUS:return +self.visit(node.expr)elif op == MINUS:return -self.visit(node.expr)向前!
讓我們手動為表達式“5 - - - 2”構建一個AST并將其傳遞給我們的解釋器以驗證新的visit_UnaryOp方法是否有效。以下是從 Python shell 執行此操作的方法:
>>> from spi import BinOp, UnaryOp, Num, MINUS, INTEGER, Token >>> five_tok = Token(INTEGER, 5) >>> two_tok = Token(INTEGER, 2) >>> minus_tok = Token(MINUS, '-') >>> expr_node = BinOp( ... Num(five_tok), ... minus_tok, ... UnaryOp(minus_token, UnaryOp(minus_token, Num(two_tok))) ... ) >>> from spi import Interpreter >>> inter = Interpreter(None) >>> inter.visit(expr_node) 3從視覺上看,上面的AST樹是這樣的:
直接從GitHub下載本文解釋器的完整源代碼。親自嘗試一下,看看您更新的基于樹的解釋器是否正確評估包含一元運算符的算術表達式。
這是一個示例會話:
$ python spi.py spi> - 3 -3 spi> + 3 3 spi> 5 - - - + - 3 8 spi> 5 - - - + - ( 3 + 4 ) - +210我還更新了genastdot.py實用程序來處理一元運算符。以下是為帶有一元運算符的表達式生成的AST圖像的一些示例:
$ python genastdot.py "- 3" > ast.dot && dot -Tpng -o ast.png ast.dot $ python genastdot.py "+ 3" > ast.dot && dot -Tpng -o ast.png ast.dot $ python genastdot.py "5 - - - + - 3" > ast.dot && dot -Tpng -o ast.png ast.dot $ python genastdot.py "5 - - - + - (3 + 4) - +2" \ > ast.dot && dot -Tpng -o ast.png ast.dot這是給你的一個新練習:
- 安裝Free Pascal,編譯并運行testunary.pas,并驗證結果與使用spi 解釋器生成的結果相同。
這就是今天的全部內容。在下一篇文章中,我們將處理賦值語句。請繼續關注,很快就會見到你。
C語言代碼(作者沒提供完整的python代碼,關鍵的改動提供了,在上面,完整的python代碼這里略掉)
#include <stdio.h> #include <stdlib.h> #include <memory.h> #include <string.h> #include<math.h>#define flag_integer 0 #define flag_plus 1 #define flag_minus 2 #define flag_multiply 3 #define flag_divide 4 #define flag_LPAREN 5 #define flag_RPAREN 6 #define flag_plus_sign 7 #define flag_minus_sign 8#define flag_EOF 7void error() {printf("程序不對勁!\n");exit(-1); }//給字符數組賦值 void StrAssign(char* T, const char* chars) {int i;for (i = 0; i < strlen(chars); i++)T[i] = *(chars + i);T[i] = '\0'; }struct Token {int type;int value; };//Token初始化函數 struct Token* mallocToken(int type, int value) {struct Token* token = (Token*)malloc(sizeof(Token));if (NULL != token) {token->type = type;token->value = value;return token;}elseerror(); }//樹的節點 struct BinOpOrNum {struct BinOpOrNum* left;struct Token* op_or_num;struct BinOpOrNum* right; };//樹結點初始化函數 struct BinOpOrNum* mallocOpOrNum(struct BinOpOrNum* left, struct Token* op_or_num, struct BinOpOrNum* right) {struct BinOpOrNum* node = (BinOpOrNum*)malloc(sizeof(BinOpOrNum));if (NULL != node) {node->left = left;node->op_or_num = op_or_num;node->right = right;return node;}elseerror(); }struct Lexer {char* text;int pos; };struct Parser {struct Lexer* lexer;struct Token current_token; };struct Interpreter {struct Parser* parser; };void skip_whitespace(struct Lexer* le) {while (le->text[le->pos] == ' ') {le->pos++;} }//判斷Interpreter中當前pos是不是數字 int is_digital(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_digital(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 Parser* par) {//先跳空格,再判斷有沒有結束符if (current_char(par->lexer) == ' ')skip_whitespace(par->lexer);if (par->lexer->pos > (strlen(par->lexer->text) - 1)) {par->current_token = { flag_EOF, NULL };return;}char current = current_char(par->lexer);if (is_digital(current)) {par->current_token = { flag_integer, integer(par->lexer) };return;}if (current == '+') {par->current_token = { flag_plus, NULL };advance(par->lexer);return;}if (current == '-') {par->current_token = { flag_minus, NULL };advance(par->lexer);;return;}if (current == '*') {par->current_token = { flag_multiply, NULL };advance(par->lexer);;return;}if (current == '/') {par->current_token = { flag_divide, NULL };advance(par->lexer);;return;}if (current == '(') {par->current_token = { flag_LPAREN, NULL };advance(par->lexer);;return;}if (current == ')') {par->current_token = { flag_RPAREN, NULL };advance(par->lexer);;return;}error();//如果都不是以上的字符,則報錯并退出程序 }int eat(struct Parser* par, int type) {int current_token_value = par->current_token.value;if (par->current_token.type == type) {get_next_token(par);return current_token_value;}else {error();} }//遍歷樹 int visit_BinOp(struct BinOpOrNum* node) {if (node->op_or_num->type == flag_plus)return visit_BinOp(node->left) + visit_BinOp(node->right);else if (node->op_or_num->type == flag_minus)return visit_BinOp(node->left) - visit_BinOp(node->right);else if (node->op_or_num->type == flag_multiply)return visit_BinOp(node->left) * visit_BinOp(node->right);else if (node->op_or_num->type == flag_divide)return visit_BinOp(node->left) / visit_BinOp(node->right);else if (node->op_or_num->type == flag_integer)return node->op_or_num->value;else if (node->op_or_num->type == flag_plus_sign)return visit_BinOp(node->right);else if (node->op_or_num->type == flag_minus_sign)return (-1) * visit_BinOp(node->right); }struct BinOpOrNum* expr(struct Parser* par);//expr定義在后面,在這里要聲明才能使用 //判斷數字或括號或正負號(加減號后出現的正負號) struct BinOpOrNum* factor(struct Parser* par) {if (par->current_token.type == flag_plus) {eat(par, flag_plus);struct Token* token = mallocToken(flag_plus_sign , NULL);struct BinOpOrNum* node = mallocOpOrNum(NULL, token, factor(par));return node;}else if (par->current_token.type == flag_minus) {eat(par, flag_minus);struct Token* token = mallocToken(flag_minus_sign, NULL);struct BinOpOrNum* node = mallocOpOrNum(NULL, token, factor(par));return node;}else if (par->current_token.type == flag_integer) {struct Token* token = mallocToken(par->current_token.type, par->current_token.value);eat(par, flag_integer);struct BinOpOrNum* node = mallocOpOrNum(NULL, token, NULL);return node;}else if (par->current_token.type == flag_LPAREN) {eat(par, flag_LPAREN);//遇到括號先吃掉,然后回到exprstruct BinOpOrNum* node = expr(par);eat(par, flag_RPAREN);return node;} }//判斷乘除 struct BinOpOrNum* term(struct Parser* par) {struct BinOpOrNum* node = factor(par);while (par->current_token.type == flag_multiply or par->current_token.type == flag_divide) {struct Token* token = mallocToken(par->current_token.type, par->current_token.value);eat(par, par->current_token.type);node = mallocOpOrNum(node, token, factor(par));}return node; }//判斷加減 struct BinOpOrNum* expr(struct Parser* par) {struct BinOpOrNum* node = term(par);while (par->current_token.type == flag_plus or par->current_token.type == flag_minus) {struct Token* token = mallocToken(par->current_token.type, par->current_token.value);eat(par, par->current_token.type);//struct BinOpOrNum* node = mallocOpOrNum(node, token, term(par));//重定義了,還報錯使用未經初始化的指針,找了半天才發現問題node = mallocOpOrNum(node, token, term(par));}return node; }struct BinOpOrNum* parser(struct Parser* par) {return expr(par); }int interpreter(struct Interpreter* ipt) {struct BinOpOrNum* tree = parser(ipt->parser);return visit_BinOp(tree); }int main() {char text[50];//StrAssign(text, "(3+2) *7");//StrAssign(text, " 3+2*(3+7)+(1 - (3+7)/5)* (2/2+3 )* 1");StrAssign(text, "5 - - - + - ( 3 + 4 ) - +2");struct Lexer le = { text, 0 };struct Parser par = { &le };get_next_token(&par);struct Interpreter ipt = { &par };int result = interpreter(&ipt);printf("%s = %d\n\n", text, result);return 0; }較上一課,主要改動了factor函數中添加正負號結點的代碼,以及visit_BinOp函數中訪問正負號結點的代碼
運行結果:
5 - - - + - ( 3 + 4 ) - +2 = 10總結
以上是生活随笔為你收集整理的【编译原理】构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 8.)(笔记)一元运算符正负(+,-)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【编译原理】构建一个简单的解释器(Let
- 下一篇: 【编译原理】构建一个简单的解释器(Let