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

文章目录

  • python代码
  • c代码
  • 总结

让我们再次深入研究解释器和编译器。

今天,我将向您展示第 1 部分中计算器的新版本,它将能够:

1、处理输入字符串中任意位置的空白字符
2、从输入中使用多位整数
3、两个整数相减(目前只能相加)

与第 1 部分的版本相比,主要的代码变化 是:

1、该get_next_token方法重构了一下。增加pos指针的逻辑被分解为一个单独的方法Advance。
2、添加了另外两种方法:skip_whitespace忽略空白字符和integer处理输入中的多位整数。
3、该EXPR方法被修改,以识别INTEGER - > MINUS - > INTEGER短语除了INTEGER - > PLUS - > INTEGER短语。该方法现在还可以在成功识别相应短语后解释加法和减法。

python代码

# -*- coding: utf-8 -*-
"""
# -*- coding: utf-8 -*-
"""
@File    : calc2.py
@Time    : 2021/7/8 10:32
@Author  : Dontla
@Email   : sxana@qq.com
@Software: PyCharm
"""
# 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", 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]def error(self):raise Exception('Error parsing input')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."""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)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 expr(self):"""Parser / Interpreterexpr -> INTEGER PLUS INTEGERexpr -> INTEGER MINUS INTEGER"""# set current token to the first token taken from the inputself.current_token = self.get_next_token()# we expect the current token to be an integerleft = self.current_tokenself.eat(INTEGER)# we expect the current token to be either a '+' or '-'op = self.current_tokenif op.type == PLUS:self.eat(PLUS)else:self.eat(MINUS)# we expect the current token to be an integerright = self.current_tokenself.eat(INTEGER)# after the above call the self.current_token is set to# EOF token# at this point either the INTEGER PLUS INTEGER or# the INTEGER MINUS INTEGER sequence of tokens# has been successfully found and the method can just# return the result of adding or subtracting two integers,# thus effectively interpreting client inputif op.type == PLUS:result = left.value + right.valueelse:result = left.value - right.valuereturn 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: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/calc2.py
calc>    33234   -   324
32910

c代码

注意,跟上一课代码不同的是,eat函数在最后才调用get_next_token函数,使得最后得到的token不是当前token而是下一个token

#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 expr(char* text) {struct Interpreter ipt = { text, 0 };get_next_token(&ipt);int left = eat(&ipt, flag_digital);//断言第一个token是数字//int left = ipt.current_token.value;int op = ipt.current_token.type;//断言第二个token是加号或减号if (op == flag_plus) {eat(&ipt, flag_plus);}else {eat(&ipt, flag_minus);}int right = eat(&ipt, flag_digital);//断言第三个token是数字int result = 0;if (op == flag_plus) {result = left + right;}else if (op == flag_minus) {result = left - right;}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请输入算式:
44 + 55
= 99请输入算式:555  +  555
= 1110请输入算式:

总结

在第 1 部分中,您学习了两个重要概念,即token标记和词法分析器lexical analyzer。今天我想谈谈词素lexeme、解析parsing和解析器parsers。

您已经了解标记。但是为了让我完成对标记的讨论,我需要提及词素。什么是词素?词素是形成一个标记的字符序列。在下图中,您可以看到一些标记和示例词素的示例,希望它可以使它们之间的关系变得清晰:

现在,还记得我们的朋友expr方法吗?我之前说过,这就是算术表达式的解释实际发生的地方。但在解释一个表达式之前,您首先需要识别它是什么类型的短语,例如,是加法还是减法。这就是expr方法本质上所做的:它在从get_next_token方法获得的标记流中找到结构,然后解释已识别的短语,生成算术表达式的结果。

在标记流中找到结构的过程,或者换句话说,识别标记流中的短语的过程称为解析。执行该工作的解释器或编译器的部分称为解析器

所以现在您知道expr方法是解析解释发生的解释器的一部分- expr方法首先尝试识别(解析)INTEGER -> PLUS -> INTEGER或INTEGER -> MINUS -> INTEGER短语标记流,在成功识别(解析)其中一个短语后,该方法会对其进行解释,并将两个整数的加法或减法结果返回给调用者。

现在又到了锻炼的时候了。

1、扩展计算器以处理两个整数的乘法
2、扩展计算器以处理两个整数的除法
3、修改代码以解释包含任意数量的加法和减法的表达式,例如“9 - 5 + 3 + 11”

检查你的理解。

1、什么是词素lexeme?【词素是形成一个标记的字符序列】
2、在标记流中找到结构的过程的名称是什么,或者换句话说,识别该标记流中某个短语的过程的名称是什么?【解析】
3、执行解析的解释器(编译器)部分的名称是什么?【解析器】

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

  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 8.)(笔记)一元运算符正负(+,-)

    [编译原理]让我们来构建一个简单的解释器(Let's Build A Simple Interpreter. Part 8.) 文章目录 C语言代码(作者没提供完整的python代码,关键的改动提供了 ...

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

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

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

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

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

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

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

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

  7. 【编译原理】让我们来构建一个简单的解释器(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 ...

  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. 学了编译原理能否用 Java 写一个编译器或解释器?

    16 个回答 默认排序​ RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和 ...

最新文章

  1. Tablayout 多个界面使用一个fragment 的实例
  2. Windows 7删需要权限的文件
  3. Exynos 4412启动过程(群聊笔记记录)
  4. $$和$BASHPID区别
  5. 原生ajax封装,数据初始化,
  6. 《云计算开放应用架构》标准发布,阿里云持续领航云原生
  7. OpenCV3学习(7.2)——图像分割之二(分水岭算法watershed)
  8. iOS开发之isa、superclass(课程总结)
  9. hdu 6205: card card card【输入挂】
  10. linux查找超过一定时间的文件,并批量删除
  11. python 生存分析,利用python进行泰坦尼克生存预测——数据探索分析
  12. 特洛伊木马程序_历史著名的特洛伊木马计,希腊的人造礼物
  13. 2016-2017 华为中兴笔试+测评+技术面+综合二面+洽谈经验
  14. spring-boot文件上传限制
  15. 计算机函数average意思,average函数的功能是计算什么
  16. SAP中总账科目行项目不显示的处理方法
  17. ArcEngine实现要素类排序的四种方法
  18. Windows Server 2016-客户端加域端口汇总
  19. VirtualBox安装Ubuntu20.04 + 安装增强功能
  20. 科学中有故事,故事中有科学

热门文章

  1. SAP Web IDE本地环境搭建
  2. 【学习笔记】Django
  3. 【PM模块】技术对象管理
  4. 【MM模块】Source Lists 货源清单
  5. 物料管理的容差范围维护
  6. Unmarshalling Error: unexpected element 错误的解决
  7. 生产订单总目标成本为0
  8. ALV中动态内表+行转化为列
  9. SAP Smartforms 条码打印
  10. 金融零售业务大数据分析解决方案