从0开始自制解释器——实现多位整数的加减法计算器
上一篇我们实现了一个简单的加法计算器,并且了解了基本的词法分析、词法分析器的概念。本篇我们将要对之前实现的加法计算器进行扩展,我们为它添加以下几个功能
- 计算减法
- 能自动识别并跳过空白字符
- 不再局限于单个整数,而是能计算多位整数
提供一些工具函数
首先为了支持减法,我们需要重新定义一下TokenType这个类型,也就是需要给 - 定义一个标志。现在我们的TokenType的定义如下
typedef enum e_TokenType
{CINT = 0,PLUS,MINUS,END_OF_FILE
}ETokenType;
由于需要支持多个整数,所以我们也不知道最终会有多少个字符,因此我们提供一个END_OF_FILE 表示我们访问到了最后一个字符,此时应该退出词法分析的过程。
另外因为整数个数不再确定,我们也就不能按照之前的提供一个固定大小的数组。虽然可以提供一个足够大的空间来作为存储数字的缓冲,但是数字少了会浪费空间。而且考虑到之后要支持自定义变量和函数,采用固定长度缓冲的方式就很难找到合适的大小,太大显得浪费空间,太小有时候无法容纳得下用户定义的变量和函数名。因此这里我们采用动态长度的字符缓冲来保存。我们提供一个DyncString 的结构来保存这些内容
#define DEFAULT_BUFFER_SIZE 16// 动态字符串结构,用于保存任意长度的字符串
typedef struct DyncString
{int nLength; // 字符长度int capacity; //实际分配的空间大小char* pszBuf; //保存字符串的缓冲
}DyncString, *LPDyncString;// 动态字符串初始化
// str: 被初始化的字符串
// size: 初始化字符串缓冲的大小,如果给0则按照默认大小分配空间
void dyncstring_init(LPDyncString str, int size);
// 动态字符串空间释放
void dyncstring_free(LPDyncString str);
//重分配动态字符串大小
void dyncstring_resize(LPDyncString str, int newSize);
//往动态字符串中添加字符
void dyncstring_catch(LPDyncString str, char c);
// 重置动态数组
void dyncstring_reset(LPDyncString str);
它们的实现如下
/*----------------------------动态数组的操作函数-------------------------------*/
void dyncstring_init(LPDyncString str, int size)
{if (NULL == str)return;if (size == 0)str->capacity = DEFAULT_BUFFER_SIZE;elsestr->capacity = size;str->nLength = 0;str->pszBuf = (char*)malloc(sizeof(char) * str->capacity);if (NULL == str->pszBuf){error("分配内存失败\n");}memset(str->pszBuf, 0x00, sizeof(char) * str->capacity);
}void dyncstring_free(LPDyncString str)
{if (NULL == str)return;str->capacity = 0;str->nLength = 0;if (str->pszBuf == NULL)return;free(str->pszBuf);
}void dyncstring_resize(LPDyncString str, int newSize)
{int size = str->capacity;for (; size < newSize; size = size * 2);char* pszStr = (char*)realloc(str->pszBuf, size);str->capacity = size;str->pszBuf = pszStr;
}void dyncstring_catch(LPDyncString str, char c)
{if (str->capacity == str->nLength + 1){dyncstring_resize(str, str->capacity + 1);}str->pszBuf[str->nLength] = c;str->nLength++;
}void dyncstring_reset(LPDyncString str)
{dyncstring_free(str);dyncstring_init(str, DEFAULT_BUFFER_SIZE);
}
/*----------------------------End 动态数组的操作函数-------------------------------*/
另外提供一些额外的工具函数,他们的定义如下
void error(char* lpszFmt, ...)
{char szBuf[1024] = "";va_list arg;va_start(arg, lpszFmt);vsnprintf(szBuf, 1024, lpszFmt, arg);va_end(arg);printf(szBuf);exit(-1);
}bool is_digit(char c)
{return (c >= '0' && c <= '9');
}
bool is_space(char c)
{return (c == ' ' || c == '\t' || c == '\r' || c == '\n');
}
主要算法
我们还是延续之前的算法,一个字符一个字符的解析,只是现在需要额外的将多个整数添加到一块作为一个整数处理。而且需要添加跳过空格的处理。
首先我们对上次的代码进行一定程度的重构。我们添加一个函数专门用来获取下一个字符
char get_next_char()
{// 如果到达字符串尾部,索引不再增加if (g_pPosition == '\0'){return '\0';}else{char c = *g_pPosition;g_pPosition++;return c;}
}
expr() 函数里面大部分结构不变,主要算法仍然是按次序获取第一个整数、获取算术运算符、获取第二个整数。只是现在的整数都变成了采用 dyncstring 结构来存储
int expr()
{int val1 = 0, val2 = 0;Token token = { 0 };dyncstring_init(&token.value, DEFAULT_BUFFER_SIZE);if (get_next_token(&token) && token.type == CINT){val1 = atoi(token.value.pszBuf);}else{printf("首个操作数必须是整数\n");dyncstring_free(&token.value);return -1;}int oper = 0;if (get_next_token(&token) && (token.type == PLUS || token.type == MINUS)){oper = token.type;}else{printf("第二个字符必须是操作符, 当前只支持+/-\n");dyncstring_free(&token.value);return -1;}if (get_next_token(&token) && token.type == CINT){val2 = atoi(token.value.pszBuf);}else{printf("操作符后需要跟一个整数\n");dyncstring_free(&token.value);return -1;}switch (oper){case PLUS:{printf("%d+%d=%d\n", val1, val2, val1 + val2);}break;case MINUS:{printf("%d-%d=%d\n", val1, val2, val1 - val2);}break;default:printf("未知的操作!\n");break;}dyncstring_free(&token.value);
}
最后就是最终要的 get_next_token 函数了。这个函数最主要的修改就是添加了解析整数和跳过空格的功能
bool get_next_token(LPTOKEN pToken)
{char c = get_next_char();dyncstring_reset(&pToken->value);if (is_digit(c)){dyncstring_catch(&pToken->value, c);pToken->type = CINT;parser_number(&pToken->value);}else if (c == '+'){pToken->type = PLUS;dyncstring_catch(&pToken->value, '+');}else if (c == '-'){pToken->type = MINUS;dyncstring_catch(&pToken->value, '-');}else if(is_space(c)){skip_whitespace();return get_next_token(pToken);}else if ('\0' == c){pToken->type = END_OF_FILE;}else{return false;}return true;
}
在这个函数中我们先获取第一个字符,如果字符是整数则获取后面的整数并直接拼接为一个完整的整数。如果是空格则跳过接下来的空格。这两个是可能要处理多个字符所以这里使用了单独的函数来处理。其余只处理单个字符可以直接返回。
parser_number 和 skip_whitespace 函数比较简单,主要的过程是不断从输入中取出字符,如果是空格则直接将索引往后移动,如果是整数则像对应的整数字符串中将整数字符加入。
void skip_whitespace()
{char c = '\0';do{c = get_next_char();} while (is_space(c));// 遇到不是空白字符的,下次要取用它,这里需要重复取用上次取出的字符g_pPosition--;
}void parser_number(LPDyncString dyncstr)
{char c = get_next_char();while(is_digit(c)){dyncstring_catch(dyncstr, c);c = get_next_char();}// 遇到不是数字的,下次要取用它,这里需要重复取用上次取出的字符g_pPosition--;
}
唯一需要注意的是,最后都有一个 g_pPosition-- 的操作。因为当我们发现下一个字符不符合条件的时候,它已经过了最后一个数字或者空格了,此时应该已经退回到get_next_token 函数中了,这个函数第一步就是获取下一个字符,因此会产生字符串被跳过的现象。所以这里我们执行 -- 退回到上一个位置,这样再取下一个就不会有问题了。
最后为了能够获取空格的输入,我们将之前的scanf 改成 gets。这样就大功告成了。
我们来测试一下结果

最后的总结
最后来一个总结。本篇我们对上一次的加法计算器进行了简单的改造,支持加减法、能跳过空格并且能够计算多位整数。
在上一篇文章中,我们提到了Token,并且说过,像 get_next_token 这样给字符串每个部分打上Token的过程就是词法分析。get_next_token 这部分代码可以被称之为词法分析器。这篇我们再来介绍一下其他的概念。
- 词位(lexeme):
词位的中文解释是语言词汇的基本单位。例如汉语的词位是汉字,英语的词位是基本的英文字母。对于我们这个加法计算器来说基本的词位就是数字以及+\-这两个符号 - parsing(语法分析)和 parser(语法分析器)
我们所编写的expr函数主要工作流程是根据token来组织代码行为。它的本质就是从Token流中识别出对应的结构,并将结构翻译为具体的行为。例如这里找到的结构是CINT oper CINT。并且将两个int按照oper指定的运算符进行算术运算。这个将Token流中识别出对应的结构的过程我们称之为语法分析,完成语法分析的组件被称之为语法分析器。expr函数中即实现了语法分析的功能,也实现了解释执行的功能。
相关文章:
从0开始自制解释器——实现多位整数的加减法计算器
上一篇我们实现了一个简单的加法计算器,并且了解了基本的词法分析、词法分析器的概念。本篇我们将要对之前实现的加法计算器进行扩展,我们为它添加以下几个功能 计算减法能自动识别并跳过空白字符不再局限于单个整数,而是能计算多位整数 提…...
(12)C#传智:File类,泛型,字典,FileStream,StreamReader,多态
内容有点多,重点:泛型、字典,流与多态。 继续深入学习内容:List、Dictionary、using语句、FileStream 一、File类的继续学心 File.ReadAllLines(string path,Encoding,encoding)指定编码读取返回行字串数组 File.WriteAllText(string…...
Dubbo的服务暴漏与服务发现源码详解
服务暴漏 如果配置需要刷新则根据配置优先级刷新服务配置 如果服务已经导出,则直接返回 是否异步导出(全局或者服务级别配置了异步,则需要异步导出服务) 服务暴漏入口DefaultModuleDeployer#exportServices private void exp…...
Python 的IDE——PyCharm
IDE介绍与安装 介绍 集成开发环境(IDE) 集成开发环境(IDE,integrated Development Environment) —— 集成开发软件需要的所有工具,一般包括以下工具: 图形用户界面 代码编辑器(支持代码补全、自动缩进) 编译器/解释器 调试器…...
01 C语言使用链表实现队列(Queue、FIFO)模块
01 C语言使用链表实现队列(Queue、FIFO)模块 作者将狼才鲸创建日期2023-03-08Gitee源码仓库地址:C语言使用链表实现队列(Queue、FIFO)模块 Linux原生的队列KFIFO一次只能操作一个队列,操作变长元素时&…...
2.2操作系统-进程管理:前趋图、前趋图与PV操作
2.1操作系统-进程管理:前趋图\前趋图与PV操作前趋图前趋图与PV操作练习前趋图与PV操作,一般出现了,分值在2~3分左右,技巧性很强。 前趋图 前趋图是为了描述一个程序的各部分间的依赖关系,或者是一个大的计算的各个子…...
凤凰游攻略
凤凰游攻略1 装备📦1.1 证件1.2 日常用品1.3 药品1.4 衣物1.5 洗漱用品2 交通🚗3 住宿🏠4 美食🍕5 拍照📷5.1 租苗族服5.1.1 单租服装5.1.2 服装化妆5.2 一条龙旅拍6 路线🗺️景点🏙️7 注意⚠️…...
Nginx 高可用方案
准备工作 10.10.4.5 10.10.4.6 VIP:10.10.4.10 两台虚拟机。安装好Nginx 安装Nginx 更新yum源文件: rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm wget -O /etc/yum.repos.d/CentOS-Ba…...
Linux基本指令
文章目录 常用Linux命令常见Linux指令 1、ls指令 语法:ls [选项][目录或文件] 功能:对于目录,该命令列出该目录下的所有子目录与文件。对于文件,将列出文件名以及其他信息。常用选项: -a 列出目录下的所有文件…...
Linux系统基础命令(二)
一、浏览和切换目录 ls命令:列出文件和目录,主要用于列出文件和目录 CentOS的终端默认是有颜色标注的。一般来说:蓝色--->目录;绿色-->可执行文件;红色--->压缩文件;浅蓝色--->链接文件&#…...
【C++】C++11——简介|列表初始|简化声明|nullptr与范围for|STL中的变化
文章目录一、C11简介二、列表初始化三、简化声明四、nullptr与范围for五、STL中一些变化一、C11简介 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1),使得C03这个名字已经取代了C98称为C11之前的最新C标准名称。不过由于TC1主要是对C98标准中的漏洞进行修复…...
Python -- 函数
文章目录1、一个简单的函数2、多参数函数3、返回值3.1、简单的返回3.2、返回列表和字典4、传入列表5、传入任意数量的实参5.1、以元组和字典的形式5.2、形参的排列顺序6、将函数储存在模块中1、一个简单的函数 函数用关键字def来定义,传参时不用指定参数类型 para&…...
Pytorch中utils.data 与torchvision简介
Pytorch中utils.data 与torchvision简介1 数据处理工具概述2 utils.data简介3 torchvision简介3.1 transforms3.2 ImageFolder1 数据处理工具概述 Pytorch涉及数据处理(数据装载、数据预处理、数据增强等)主要工具包及相互关系如下图所示,主…...
学习 Python 之 Pygame 开发魂斗罗(十)
学习 Python 之 Pygame 开发魂斗罗(十)继续编写魂斗罗1. 解决敌人不开火的问题2. 创建爆炸效果类3. 为敌人跳入河中增加爆炸效果4. 玩家击中敌人继续编写魂斗罗 在上次的博客学习 Python 之 Pygame 开发魂斗罗(九)中,…...
Keepalive+LVS群集部署
KeepaliveLVS群集部署一、Keepalive概述1、什么是Keepalive2、Keepalive工作原理3、Keepalive主要模块及作用4、Keepalived 服务重要功能(1)管理 LVS 负载均衡软件(2)支持故障自动切换(3)实现 LVS 负载调度…...
数组、指针总结【面试题】
文章目录0. 补充知识数组笔试题1. 一维数组1.1 字符数组1.1.1 sizeof1.1.2 strlen1.2 二维数组2. 指针笔试题0. 补充知识 在进入数组与指针的练习时,我们先来复习以下以下的知识点,这可以帮助我们更好的理解下面练习 数组是一组能存放相同类型的类型的元…...
七色电子标签
机种名 电子会议桌牌 型号 ESL_7color_7.3_D 外观尺寸 176.2x137.15x80mm 产品重量 268g 可视区域 163.297.92mm 外观颜色 银色 供电方式 锂电池供电2300mAh(Type-C 接口可充电) 显示技术 E-INK电子纸,双屏 像素 800x480 像…...
大数据是什么?发展前景怎么样
关于大数据的解释,比较官方的定义是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。简单来说,大数据就是结构化…...
MYSQL必知必会 | 查询相关
汇总数据 聚集函数 有时只需要汇总数据,并不需要把数据实际检索出来,所以MySql提供了专门的函数 聚集函数:运行在行组上,计算和返回单个值的函数 函数说明AVG()返回某列平均值COUNT()返回某列的行数MAX()返回某列最大值MIN()返…...
Java学习环境一站说明(保姆级详细教学)
1.Java开发环境搭建官网下载www.oracle.com2.安装注意:1.选择安装位置时尽量不要安装到C盘,路径中不要有空格以及中文的存在2.开发人员安装的jdk中包含了jre,所以不需要单独安装jre3.环境变量配置打开高级系统设置2.点击环境变量3.在系统变量…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
