【编译原理】让我们来构建一个简单的解释器(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.)(笔记)一元运算符正负(+,-)相关推荐

  1. 【编译原理】构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 9.)(笔记)语法分析(未完,先搁置了!)

    [编译原理]让我们来构建一个简单的解释器(Let's Build A Simple Interpreter. Part 9.) 文章目录 spi.py spi_lexer 我记得当我在大学(很久以前) ...

  2. 【编译原理】构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 7.)(笔记)解释器 interpreter 解析器 parser 抽象语法树AST

    [编译原理]让我们来构建一个简单的解释器(Let's Build A Simple Interpreter. Part 7.) 文章目录 python代码 插--后序遍历 C语言代码(有错误) C语言 ...

  3. 【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 6.)(python/c/c++版)(笔记)

    [编译原理]让我们来构建一个简单的解释器(Let's Build A Simple Interpreter. Part 6.) 文章目录 python代码 C语言代码 总结 今天是这一天:) &quo ...

  4. 【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 5.)(python/c/c++版)(笔记)Lexer词法分析程序

    [编译原理]让我们来构建一个简单的解释器(Let's Build A Simple Interpreter. Part 5.) 文章目录 python代码 C语言代码 总结 你如何处理像理解如何创建解 ...

  5. 【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 4.)(python/c/c++版)(笔记)

    [编译原理]让我们来构建一个简单的解释器(Let's Build A Simple Interpreter. Part 4.) 文章目录 python代码 C语言代码 总结 在上一篇文章中,您学习了如 ...

  6. 【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 3.)(python/c/c++版)(笔记)

    [编译原理]让我们来构建一个简单的解释器(Let's Build A Simple Interpreter. Part 3.) 文章目录 python代码calc3.py C语言代码(calc3.cp ...

  7. 【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 2.)(python/c/c++版)(笔记)

    [编译原理]让我们来构建一个简单的解释器(Let's Build A Simple Interpreter. Part 2.) 文章目录 python代码 c代码 总结 让我们再次深入研究解释器和编译 ...

  8. 【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 1.)(python/c/c++版)(笔记)

    原文:Let's Build A Simple Interpreter. Part 1. 文章目录 [编译原理]让我们来构建一个简单的解释器(Let's Build A Simple Interpre ...

  9. 如何只使用标签来构建一个简单的电影推荐系统

    作者:Johnson Kuan   编译:ronghuaiyang 导读 使用基于内容的方法来找到最相似的电影. No MIT chalkboard required 介绍 假设你正在推出下一个非常大 ...

最新文章

  1. C# 面向对象之继承后初始化顺序
  2. 不用代码,10分钟打造属于自己的第一款小程序
  3. Cpp / 通用引用、引用折叠与完美转发问题
  4. VTK:可视化之ExtrudePolyDataAlongLine
  5. Natasha 4.0 探索之路系列(四) 模板 API
  6. 单向链表的建立和简单的增删改查
  7. oracle 查询数据 实验笔记三
  8. SQL Server 2016 开发版将免费
  9. 查看二进制文件,报错 You are not using binary logging
  10. windows环境tomcat8配置Solr5.5.1
  11. Lpl and Energy-saving Lamps
  12. oracle避免索引失效,Oracle优化你的查询--关于避免索引隐式失效
  13. 小菜的项目管理修炼之道
  14. 银行卡收单之网络传输加密
  15. 应聘的16个经典面试问题回答思路
  16. 飞机大战游戏python_《飞》字意思读音、组词解释及笔画数 - 新华字典 - 911查询...
  17. 攻防世界_moile
  18. 在matlab中怎么把点链接,怎么样把所有点连接起来?
  19. 康得新董事长是谁?_您人生董事会中的谁?
  20. CrossCompiler And Auto tools

热门文章

  1. Spring学习之Bean的配置
  2. CentOS 5.5升级内核到2.6.35.4
  3. 【SAP】相关性类型简介
  4. 企业使用MES系统的好处以及使用前后的区别
  5. 大洋洲群狼来了! 这是中国篮球学习契机?
  6. SAP FICO期初开账存货导入尾差
  7. 安装SAP Business One对软硬件有哪些要求
  8. 关于SMARTFORM的页数问题
  9. 转型会员制,云集能否讲好电商下半场故事?
  10. python3堆排序_python 堆排序