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

文章目录

  • python代码
  • C语言代码
  • 总结

今天是这一天:) “为什么?” 你可能会问。原因是今天我们结束了对算术表达式的讨论(好吧,几乎)通过在我们的语法中添加括号表达式并实现一个解释器,该解释器将能够评估具有任意深度嵌套的括号表达式,例如表达式 7 + 3 * (10 / (12 / (3 + 1) - 1))。

让我们开始吧,好吗?

首先,让我们修改语法以支持括号内的表达式。正如您在第 5 部分 中记得的那样,因子规则用于表达式中的基本单位。在那篇文章中,我们唯一的基本单位是整数。今天我们要添加另一个基本单位——括号表达式。我们开始做吧。

这是我们更新的语法:

的EXPR和术语制作完全相同如在第5部分和唯一的变化是在因子生产其中终端LPAREN表示左括号“”,终端RPAREN表示右括号“”,和非括号之间的终结符 expr指的是expr 规则。

这是factor的更新语法图,现在包括替代方案:

(翻译不完全准确,将就着看吧!大致也能看懂)

因为expr和术语的语法规则没有改变,它们的语法图看起来与第 5 部分中的相同:

这是我们新语法的一个有趣特性——它是递归的。如果您尝试推导表达式 2 * (7 + 3),您将从expr开始符号开始,最终您将再次递归使用expr规则推导 (7 + 3) 部分原始算术表达式。

让我们根据语法对表达式 2 * (7 + 3) 进行分解,看看它的样子:

顺便说一句:如果您需要复习递归,请看一看 Daniel P. Friedman 和 Matthias Felleisen 的The Little Schemer一书——它真的很棒。

好的,让我们开始吧,将我们新更新的语法翻译成代码。

以下是对上一篇文章中代码的主要更改:

该词法已被修改为返回两个标记:LPAREN的左括号和RPAREN一个右括号。
该解释的因素法已略有更新解析除了整数括号表达式。
这是可以计算包含整数的算术表达式的计算器的完整代码;任意数量的加法、减法、乘法和除法运算符;和带有任意深度嵌套的括号表达式:

python代码

# -*- coding: utf-8 -*-
"""
@File    : calc6.py
@Time    : 2021/7/21 10:00
@Author  : Dontla
@Email   : sxana@qq.com
@Software: PyCharm
"""
# Token types
#
# EOF (end-of-file) token is used to indicate that
# there is no more input left for lexical analysis
INTEGER, PLUS, MINUS, MUL, DIV, LPAREN, RPAREN, EOF = ('INTEGER', 'PLUS', 'MINUS', 'MUL', 'DIV', '(', ')', 'EOF'
)class Token(object):def __init__(self, type, value):self.type = typeself.value = valuedef __str__(self):"""String representation of the class instance.Examples:Token(INTEGER, 3)Token(PLUS, '+')Token(MUL, '*')"""return 'Token({type}, {value})'.format(type=self.type,value=repr(self.value))def __repr__(self):return self.__str__()class Lexer(object):def __init__(self, text):# client string input, e.g. "4 + 2 * 3 - 6 / 2"self.text = text# self.pos is an index into self.textself.pos = 0self.current_char = self.text[self.pos]def error(self):raise Exception('Invalid character')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, '-')if self.current_char == '*':self.advance()return Token(MUL, '*')if self.current_char == '/':self.advance()return Token(DIV, '/')if self.current_char == '(':self.advance()return Token(LPAREN, '(')if self.current_char == ')':self.advance()return Token(RPAREN, ')')self.error()return Token(EOF, None)class Interpreter(object):def __init__(self, lexer):self.lexer = lexer# set current token to the first token taken from the inputself.current_token = self.lexer.get_next_token()def error(self):raise Exception('Invalid syntax')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.lexer.get_next_token()else:self.error()def factor(self):"""factor : INTEGER | LPAREN expr RPAREN"""token = self.current_tokenif token.type == INTEGER:self.eat(INTEGER)return token.valueelif token.type == LPAREN:self.eat(LPAREN)result = self.expr()self.eat(RPAREN)return resultdef term(self):"""term : factor ((MUL | DIV) factor)*"""result = self.factor()while self.current_token.type in (MUL, DIV):token = self.current_tokenif token.type == MUL:self.eat(MUL)result = result * self.factor()elif token.type == DIV:self.eat(DIV)result = result / self.factor()return resultdef expr(self):"""Arithmetic expression parser / interpreter.calc> 7 + 3 * (10 / (12 / (3 + 1) - 1))22expr   : term ((PLUS | MINUS) term)*term   : factor ((MUL | DIV) factor)*factor : INTEGER | LPAREN expr RPAREN"""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> ')text = input('calc> ')except EOFError:breakif not text:continuelexer = Lexer(text)interpreter = Interpreter(lexer)result = interpreter.expr()print(result)if __name__ == '__main__':main()

运行结果:

D:\python_virtualenv\my_flask\Scripts\python.exe C:/Users/Administrator/Desktop/编译原理/python/calc6.py
calc>  5 * ( 3 - 2)/(2 -1) + ( 4 - 2)
7.0
calc>

C语言代码

将首次初始化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_multiply 3
#define flag_divide 4
#define flag_LPAREN 5
#define flag_RPAREN 6#define flag_EOF 5struct Token
{int type;int value;
};struct Lexer
{char* text;int pos;
};struct Interpreter
{struct Lexer* lexer;struct Token current_token;
};void error() {printf("输入非法!\n");exit(-1);
}void skip_whitespace(struct Lexer* le) {while (le->text[le->pos] == ' ') {le->pos++;}
}//判断Interpreter中当前pos是不是数字
int is_integer(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_integer(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 Interpreter* pipt) {//先跳空格,再判断有没有结束符if (current_char(pipt->lexer) == ' ')skip_whitespace(pipt->lexer);if (pipt->lexer->pos > (strlen(pipt->lexer->text) - 1)) {pipt->current_token = { flag_EOF, NULL };return;}char current = current_char(pipt->lexer);if (is_integer(current)) {pipt->current_token = { flag_digital, integer(pipt->lexer)};return;}if (current == '+') {pipt->current_token = { flag_plus, NULL };advance(pipt->lexer);return;}if (current == '-') {pipt->current_token = { flag_minus, NULL };advance(pipt->lexer);;return;}if (current == '*') {pipt->current_token = { flag_multiply, NULL };advance(pipt->lexer);;return;}if (current == '/') {pipt->current_token = { flag_divide, NULL };advance(pipt->lexer);;return;}if (current == '(') {pipt->current_token = { flag_LPAREN, NULL };advance(pipt->lexer);;return;}if (current == ')') {pipt->current_token = { flag_RPAREN, NULL };advance(pipt->lexer);;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(struct Interpreter* pipt);//expr定义在后面,在这里要声明才能使用
int factor(struct Interpreter* pipt) {if (pipt->current_token.type == flag_digital) {return eat(pipt, flag_digital);}else if(pipt->current_token.type == flag_LPAREN) {eat(pipt, flag_LPAREN);int result = expr(pipt);eat(pipt, flag_RPAREN);return result;}}//判断乘除
int term(struct Interpreter* pipt) {int result = factor(pipt);while (true) {int token_type = pipt->current_token.type;if (token_type == flag_multiply) {eat(pipt, flag_multiply);result = result * factor(pipt);}else if (token_type == flag_divide) {eat(pipt, flag_divide);result = result / factor(pipt);}else {return result;}}
}
int expr(struct Interpreter* pipt) {int result = term(pipt);while (true) {int token_type = pipt->current_token.type;if (token_type == flag_plus) {eat(pipt, flag_plus);result = result + term(pipt);}else if (token_type == flag_minus) {eat(pipt, flag_minus);result = result - term(pipt);}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';struct Lexer le = {text, 0};struct Interpreter ipt = { &le };get_next_token(&ipt);int result = expr(&ipt);printf("= %d\n\n", result);}return 0;
}

运行结果:

请输入算式:
3+3
= 6请输入算式:
4/2
= 2请输入算式:
2+3-4
= 1请输入算式:
3+(3+2)
= 8请输入算式:
2*(4+3)
= 14请输入算式:
2*(5  +3 )  - (3+4)
= 9

总结

如本文所述,编写您自己的算术表达式解释器版本。请记住:重复是所有学习之母。

嘿嘿,你一直读到最后!恭喜,您刚刚学会了如何创建(如果您已经完成了练习 - 您实际上已经编写了)一个可以计算非常复杂的算术表达式的基本递归下降解析器/解释器。

在下一篇文章中,我将更详细地讨论递归下降解析器。我还将在解释器和编译器构造中介绍一种重要且广泛使用的数据结构,我们将在整个系列中使用它。

请继续关注,很快就会见到你。在此之前,继续为您的解释器工作,最重要的是:玩得开心,享受这个过程!

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

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

最新文章

  1. debian7get源,自动设置get源
  2. 2021邹城高考成绩查询,@邹城考生 2021高考时间、考场分布图来啦!
  3. 基于DEAP库的python进化算法--遗传算法实践--背包问题
  4. 第二季-专题11-世界一下变大了-MMU
  5. 本草纲目pdf彩图版下载_本草纲目彩色图谱下载|本草纲目彩色图集(精编珍藏版) PDF电子版 - 天天游戏吧...
  6. jabber android apk,Android版Jabber — 快速入门指南
  7. C++解压zip压缩文件
  8. Google 开源项目风格指南学习笔记——C++篇
  9. Android主界面退出再次进入略过欢迎面
  10. Python写反斜杠\a
  11. ImageJ自动批量荧光面积统计
  12. python txt文本转json格式
  13. truffle unbox react报错解决方案(linux)
  14. 龙芯9341屏驱动开发过程(GUI、增加tty 裸奔版)
  15. Git error: cannot spawn ssh: No such file or directory的一个解决办法
  16. 前端基础全面的面试题
  17. 提问的艺术 for ChatGPT
  18. 服务器操作系统市场占有率,服务器操作系统市场Windows占主导地位
  19. 详解函数中的 arguments
  20. ASP.NET MVC 中的路由传递参数

热门文章

  1. 权限设计中的数据灵活存储设计策略参考[以不变应万变]
  2. 菜鸟教程之Microsoft Windows Powershell
  3. java 爬虫 保存cookies_写爬虫,免不了要研究JavaScript设置cookies的问题
  4. 测试无数据_无数据驱动自动化测试
  5. 学好计算机科学的诀窍,【教学方法论文】计算机科学技术专业高效教学方法(共4295字)...
  6. 维护库存地点的装运点确认
  7. 【美文】没有人会根据你平庸的现在就能推断出你辉煌的将来
  8. 一个项目的性能优化经验
  9. 取得成本中心组、成本要素组层级的几个BAPI
  10. 支付宝不止怀有“社交梦”,社区金融才是它的野心所在