【编译原理】让我们来构建一个简单的解释器(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)?一个语法图是一种编程语言的语法规则的图示。基本上,语法图直观地向您展示了您的编程语言中允许哪些语句,哪些不允许。

语法图很容易阅读:只需按照箭头指示的路径进行操作即可。一些路径表示选择(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循环。在循环内部,解析器根据标记(无论是加号还是减号)做出选择。花一些时间向自己证明上面的代码确实遵循算术表达式的语法图流程。

解析器本身并不解释任何东西:如果它识别出一个表达式,它就会保持沉默,如果没有,它会抛出一个语法错误。让我们修改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
5

C语言代码(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是投喂所提供的标记流饲料词法分析器,并试图在该流识别的结构,在解析器之后生成结果的解释器已成功解析(识别)一个有效的算术表达式。将这些碎片串在一起。花一些时间将您获得的知识翻译成算术表达式的工作解释器。

检查你的理解。

什么是语法图?
什么是语法分析?
什么是语法分析器?

【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 3.)(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 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. 学了编译原理能否用 Java 写一个编译器或解释器?

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

最新文章

  1. Technology Document Guide of TensorRT
  2. java并发订单号生成
  3. 截取指定网站Html编码
  4. python 结果写入excel_python中如何将测试结果写入到原有的excel表格(二)
  5. 10万,买一辆自驾无忧的智能汽车
  6. hdu4400 BFS+STL
  7. 037_JDK的Iterator接口
  8. [转载]Windows Phone 系列- 本地数据存储
  9. AngularJS学习笔记一:简单入门
  10. MobileNet V2 复现
  11. [转].NET 数据库连接池
  12. SAP Spartacus user form页面的css设计重构
  13. 发送结构化网络数据Server端
  14. Java 中 modifer #39;public#39; is reduntant for interface methods
  15. 常用的正则化方法总结
  16. spark学习-69-源代码:Endpoint模型介绍(1)
  17. python高维数据的读取_索引访问高维数组_Python数据分析与科学计算基础篇1:NumPy图解,使抽象的数据具象为可触摸的图形_深度学习视频-51CTO学院...
  18. 20190825 On Java8 第十三章 函数式编程
  19. macos big sur 11 完整离线安装包v11.5.2正式版
  20. matlab上位机电机,基于MATLAB的电机综合性能测试系统上位机软件设计

热门文章

  1. 搭建简易Linux局网服务器
  2. 设置socket.Receive()的等待时延
  3. 中怎么构建ebug模式_自动挡中的“手自一体”,该怎么用?多少人把手动模式当成摆设?...
  4. 2K17能力值上90的11位球员,你怎么看?
  5. SAP PP生产订单相关信息的获取
  6. 服务器手工修改虚拟内存,服务器修改虚拟内存
  7. 做折线图的软件_三星Galaxy Labs,它由4个下层软件构成,各有其用
  8. python输出杨辉三角啊二维数组_Java输出杨辉三角形(使用二维数组)
  9. android u盘拷贝文件大小,用手机U盘备份或导出手机文件,还能扩容
  10. android预加载布局,Android 懒加载优化